././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.672998 pyraf-2.2.1/0000755000175000017500000000000014223000071011350 5ustar00olesoles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.652998 pyraf-2.2.1/.github/0000755000175000017500000000000014223000071012710 5ustar00olesoles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.652998 pyraf-2.2.1/.github/workflows/0000755000175000017500000000000014223000071014745 5ustar00olesoles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/.github/workflows/citest.yml0000644000175000017500000000617214214070451017002 0ustar00olesoles name: PyRAF CI test on: [push] jobs: tests: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} env: tmp: /tmp/ iraf: ${{ matrix.iraf }} TERM: dumb PYRAF_NO_DISPLAY: no strategy: matrix: include: - name: Ubuntu 20.04 (Python-3.8.2), native os: ubuntu-20.04 method: native iraf: /usr/lib/iraf/ upload_coverage: yes - name: Ubuntu 20.04 (Python-3.8.2), native, without IRAF os: ubuntu-20.04 method: native - name: Ubuntu 18.04 (Python-3.7), pip os: ubuntu-18.04 method: pip iraf: /usr/lib/iraf/ - name: macOS 10.15, pip os: macos-10.15 method: pip iraf: /Users/runner/work/iraf/ steps: - name: Checkout repository if: matrix.method == 'native' uses: actions/checkout@v2 - name: Setup dependencies if: startsWith(matrix.os, 'ubuntu') && matrix.method == 'native' run: | sudo apt-get update if [ "$iraf" ]; then sudo apt-get install --no-install-recommends iraf iraf-noao iraf-dev else echo "PYRAF_NO_IRAF=yes" >> $GITHUB_ENV fi sudo apt-get install --no-install-recommends build-essential libx11-dev sudo apt-get install --no-install-recommends python3-dev python3-pip python3-astropy python3-numpy-dev python3-setuptools python3-setuptools-scm python3-tk python3-pytest python3-pytest-cov ipython3 pip3 install "stsci.tools>=4.0.1" coveragepy - name: Setup dependencies if: startsWith(matrix.os, 'ubuntu') && matrix.method == 'pip' run: | sudo apt-get update sudo apt-get install --no-install-recommends iraf iraf-noao iraf-dev build-essential libx11-dev sudo apt-get install --no-install-recommends python3-dev python3-pip pip3 install ipython - name: Setup dependencies, Mac if: startsWith(matrix.os, 'macos') && matrix.method == 'pip' run: | mkdir $iraf curl https://cloud.aip.de/index.php/s/iPj7LGxbRedYnqa/download/iraf-macintel.tar.gz | tar -C $iraf -x -z (cd $iraf ; ./install < /dev/null || true) export PATH=${HOME}/.iraf/bin:${PATH} mkiraf -t=$TERM -n echo "PATH=$PATH" >> $GITHUB_ENV - name: Build PyRAF locally if: matrix.method == 'native' run: | python3 setup.py build_ext -i - name: Install PyRAF via pip if: matrix.method == 'pip' run: | pip3 install git+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=pyraf[test] - name: Run tests (locally built) if: matrix.method == 'native' run: | python3 -m pytest --cov=pyraf - name: Run tests (installed package) if: matrix.method == 'pip' run: | python3 -m pytest -s --pyargs pyraf - name: "Upload coverage to Codecov" if: matrix.upload_coverage == 'yes' uses: codecov/codecov-action@v1 with: fail_ci_if_error: false ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/.gitignore0000644000175000017500000000076114203121554013354 0ustar00olesoles# Compiled files *.py[cod] *.a *.o *.so __pycache__ # Other generated files pyraf/version.py login.cl* htmlcov .coverage MANIFEST # Sphinx docs/api docs/_build # Eclipse editor project files .project .pydevproject .settings # Pycharm editor project files .idea # Packages/installer info *.egg* *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg distribute-*.tar.gz # Other .cache .tox .*.sw[op] *~ cmd*.cl .pytest_cache/ clcache.v2.sqlite3 uparm # Mac OSX .DS_Store ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/.readthedocs.yaml0000644000175000017500000000025414203121554014610 0ustar00olesolesversion: 2 sphinx: configuration: docs/conf.py formats: [] python: version: 3.8 install: - method: pip path: . extra_requirements: - docs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/CODE_OF_CONDUCT.md0000644000175000017500000000542514203121554014165 0ustar00olesoles# Code of Conduct We are committed to providing a strong and enforced code of conduct and expect everyone in our community to follow these guidelines when interacting with others in all forums. Our goal is to keep ours a positive, inclusive, successful, and growing community. The community of participants in open source Astronomy projects is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences success and continued growth. As members of the community, - We pledge to treat all people with respect and provide a harassment- and bullying-free environment, regardless of sex, sexual orientation and/or gender identity, disability, physical appearance, body size, race, nationality, ethnicity, and religion. In particular, sexual language and imagery, sexist, racist, or otherwise exclusionary jokes are not appropriate. - We pledge to respect the work of others by recognizing acknowledgment/citation requests of original authors. As authors, we pledge to be explicit about how we want our own work to be cited or acknowledged. - We pledge to welcome those interested in joining the community, and realize that including people with a variety of opinions and backgrounds will only serve to enrich our community. In particular, discussions relating to pros/cons of various technologies, programming languages, and so on are welcome, but these should be done with respect, taking proactive measure to ensure that all participants are heard and feel confident that they can freely express their opinions. - We pledge to welcome questions and answer them respectfully, paying particular attention to those new to the community. We pledge to provide respectful criticisms and feedback in forums, especially in discussion threads resulting from code contributions. - We pledge to be conscientious of the perceptions of the wider community and to respond to criticism respectfully. We will strive to model behaviors that encourage productive debate and disagreement, both within our community and where we are criticized. We will treat those outside our community with the same respect as people within our community. - We pledge to help the entire community follow the code of conduct, and to not remain silent when we see violations of the code of conduct. We will take action when members of our community violate this code. This code of conduct applies to all community situations online and offline, including mailing lists, forums, social media, conferences, meetings, associated social events, and one-to-one interactions. Parts of this code of conduct have been adapted from the Astropy and Numfocus codes of conduct. http://www.astropy.org/code_of_conduct.html https://www.numfocus.org/about/code-of-conduct/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1643704417.0 pyraf-2.2.1/LICENSE.txt0000644000175000017500000000267014176170141013215 0ustar00olesolesCopyright (C) 2003 Association of Universities for Research in Astronomy (AURA) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of AURA and its representatives may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/MANIFEST.in0000644000175000017500000000032714203121554013120 0ustar00olesolesinclude LICENSE.txt include README.rst include setup.py include setup.cfg include MANIFEST.in recursive-include pyraf * recursive-include tools *.py global-exclude *.pyc *.o *.so *.sqlite3 recursive-inclulde docs * ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.676998 pyraf-2.2.1/PKG-INFO0000644000175000017500000001006414223000071012446 0ustar00olesolesMetadata-Version: 2.1 Name: pyraf Version: 2.2.1 Summary: Pythonic interface to IRAF that can be used in place of the existing IRAF CL Home-page: https://iraf-community.github.io/pyraf.html Author: Rick White, Perry Greenfield, Chris Sontag, Ole Streicher License: BSD 3-Clause Keywords: astronomy,astrophysics,utility Platform: any Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Scientific/Engineering :: Astronomy Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: docs Provides-Extra: test License-File: LICENSE.txt ===== PyRAF ===== |Release| |CI Status| |Coverage Status| |Documentation| PyRAF is a command language for running IRAF tasks in a Python like environment. It works very similar to IRAF, but has been updated to allow such things as importing Python modules, starting in any directory, GUI parameter editing and help. Most importantly, it can be imported into Python allowing you to run IRAF commands from within a larger script. Installation ------------ To install PyRAF, it is required to have both IRAF_ and Python_ already installed. For the IRAF installation, both a self-compiled and a binary IRAF package (f.e. in Ubuntu_) will work. The IRAF installation should have a properly configured environment, especially the ``iraf`` environment variable must be set to point to the IRAF installation directory (i.e. to ``/usr/lib/iraf/`` on Ubuntu or Debian systems). On multi-arch IRAF installations, the ``IRAFARCH`` environment variable should specify the architecture to use. This is usually already set during the IRAF installation procedure. The minimal Python required for PyRAF is 3.6, but it is recommended to use the latest available version. An installation in an virtual environment like venv_ or conda_ is possible. The package can be installed from PyPI_ with the command ``pip3 install pyraf``. Note that if no binary installation is available on PyPI, the package requires a compilation, so aside from pip3, the C compiler and development libraries (on Linux ``libx11-dev``) should be installed. Contributing Code, Documentation, or Feedback --------------------------------------------- IRAF and PyRAF can only survive by the contribution of its users, so we welcome and encourage your contributions. The preferred way to report a bug is to create a new issue on the `PyRAF GitHub issue `_ page. To contribute patches, we suggest to create a `pull request on GitHub `_. License ------- PyRAF is licensed under a 3-clause BSD style license - see the `LICENSE.txt `_ file. Documentation ------------- * `The PyRAF Tutorial `_ .. |CI Status| image:: https://github.com/iraf-community/pyraf/actions/workflows/citest.yml/badge.svg :target: https://github.com/iraf-community/pyraf/actions :alt: Pyraf CI Status .. |Coverage Status| image:: https://codecov.io/gh/iraf-community/pyraf/branch/main/graph/badge.svg :target: https://codecov.io/gh/iraf-community/pyraf :alt: PyRAF Coverage Status .. |Release| image:: https://img.shields.io/github/release/iraf-community/pyraf.svg :target: https://github.com/iraf-community/pyraf/releases/latest :alt: Pyraf release .. |Documentation| image:: https://readthedocs.org/projects/pyraf/badge/?version=latest :target: https://pyraf.readthedocs.io/en/latest/ :alt: Documentation Status .. _Python: https://www.python.org/ .. _venv: https://docs.python.org/3/library/venv.html .. _conda: https://docs.conda.io/ .. _PyPI: https://pypi.org/project/pyraf .. _IRAF: https://iraf-community.github.io .. _iraf-community: https://iraf-community.github.io .. _Ubuntu: https://www.ubuntu.com/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/README.rst0000644000175000017500000000623714214070451013060 0ustar00olesoles===== PyRAF ===== |Release| |CI Status| |Coverage Status| |Documentation| PyRAF is a command language for running IRAF tasks in a Python like environment. It works very similar to IRAF, but has been updated to allow such things as importing Python modules, starting in any directory, GUI parameter editing and help. Most importantly, it can be imported into Python allowing you to run IRAF commands from within a larger script. Installation ------------ To install PyRAF, it is required to have both IRAF_ and Python_ already installed. For the IRAF installation, both a self-compiled and a binary IRAF package (f.e. in Ubuntu_) will work. The IRAF installation should have a properly configured environment, especially the ``iraf`` environment variable must be set to point to the IRAF installation directory (i.e. to ``/usr/lib/iraf/`` on Ubuntu or Debian systems). On multi-arch IRAF installations, the ``IRAFARCH`` environment variable should specify the architecture to use. This is usually already set during the IRAF installation procedure. The minimal Python required for PyRAF is 3.6, but it is recommended to use the latest available version. An installation in an virtual environment like venv_ or conda_ is possible. The package can be installed from PyPI_ with the command ``pip3 install pyraf``. Note that if no binary installation is available on PyPI, the package requires a compilation, so aside from pip3, the C compiler and development libraries (on Linux ``libx11-dev``) should be installed. Contributing Code, Documentation, or Feedback --------------------------------------------- IRAF and PyRAF can only survive by the contribution of its users, so we welcome and encourage your contributions. The preferred way to report a bug is to create a new issue on the `PyRAF GitHub issue `_ page. To contribute patches, we suggest to create a `pull request on GitHub `_. License ------- PyRAF is licensed under a 3-clause BSD style license - see the `LICENSE.txt `_ file. Documentation ------------- * `The PyRAF Tutorial `_ .. |CI Status| image:: https://github.com/iraf-community/pyraf/actions/workflows/citest.yml/badge.svg :target: https://github.com/iraf-community/pyraf/actions :alt: Pyraf CI Status .. |Coverage Status| image:: https://codecov.io/gh/iraf-community/pyraf/branch/main/graph/badge.svg :target: https://codecov.io/gh/iraf-community/pyraf :alt: PyRAF Coverage Status .. |Release| image:: https://img.shields.io/github/release/iraf-community/pyraf.svg :target: https://github.com/iraf-community/pyraf/releases/latest :alt: Pyraf release .. |Documentation| image:: https://readthedocs.org/projects/pyraf/badge/?version=latest :target: https://pyraf.readthedocs.io/en/latest/ :alt: Documentation Status .. _Python: https://www.python.org/ .. _venv: https://docs.python.org/3/library/venv.html .. _conda: https://docs.conda.io/ .. _PyPI: https://pypi.org/project/pyraf .. _IRAF: https://iraf-community.github.io .. _iraf-community: https://iraf-community.github.io .. _Ubuntu: https://www.ubuntu.com/ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.656998 pyraf-2.2.1/docs/0000755000175000017500000000000014223000071012300 5ustar00olesoles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.656998 pyraf-2.2.1/docs/_static/0000755000175000017500000000000014223000071013726 5ustar00olesoles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/docs/_static/brand.css0000644000175000017500000000023614203121554015537 0ustar00olesolesdiv.topbar a.brand { background: url("pyraflogo.png") no-repeat 10px 4px; background-size: 32px 32px; } div.topbar ul li a.homelink { display: none; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/docs/_static/pyraflogo.png0000644000175000017500000001416614203121554016456 0ustar00olesolesPNG  IHDR\X.gAMA a cHRMz&u0`:pQ<PLTE㪶ݡڈΙ{bx[okSjrĆaoObKZKX9Hzx~6Aib`r_ۼ֩tg~ztGn̆NRuhF!wO$f3ю8='T6o(՚Fݧ^cTUŊ>̙fVGBw0] a1+ B<>2&Q}}&V1r7@Z /I#ȗEbٯIob `|1ȧ]f ^@>[@ts-Li׸mg0W[޻Y<0@"4ZܨUQ_~Vo 4CEOj'3XbyڋWb'jר#ʋk8`]dL@O/(ŋuo8Ҷ-q\gVigY0OÆL__ }?xBצVv伮___3Gw@V9ԵA鏟]E^q6飵Z7vk3C8$G7vk3 "׈~l؇ 倵uL{α[Z+T1v9|K{i]Pᐔ- 3hSY[s?rT p yX:z~#/k;.Uȇpl{ /4GyxPiH?‹zKEP犞RT)G*v؏P(M?)8 <%U 8fSU*[d0cZR6_3=^mߵʯ'IՂxjF#u+p(8#ql8Vk\0@,3DNDh\zQRݙ[/ M>4OSoȱt6chZxJnfy^s-{ioKߊ JCC9hGO)8ʎthUr ]A*ox8>P vCˤ9?U9q`KtKf2s((g!^&$.A^䱬vx ,<Vk}.u rG,Mwy*Usߎơ8VbZ8nFq닞.O8`8A*c ??}u: `3_zLCnx 4qQmnA Ǯ#rcq/p,.NyZo xa&:Ioo'(`ai)c,7rst=$G"|3hڑC8 GQ_nM(E,Vǣ < wzZcV89o/u2q؞@z s=,/ݢjڇ8JpҬqУ-80f\(L+QcQ4aLr8FX9R)ߖ@UE8nWKv a0$N)t,\uD:s#bƑ9\- & ?Hg&-w m,`0mzSQ'h,b$RQHuд,JX&MD&ZJݒ%3A ZNRKeߓzv@)RfK !WE^x[0Gz0n*z6|bXr ^Dր R)wFE n q`^^ĸkxeXpC/kJ!cS-&ϱy8j'#g>%}H=JƜ ,#£vj=Ĵ1Y) ("A>$t::%t[-v{@nÛuH#=*U"b#d)E΋K5{GpyQ|O!FQEƀ,@eEiY@v %r߼4"nw<*܎JbHG B(G 96\6 {oa=,܇*ٹ%" P0?bI%!m3T T#FOhJ^C*s;  A8p[1@sT5rBUDoC*Nbf@<ϴ h{!8܎[.+7 _aK(ԑ%brcEIs`-12 qTUi`4\r(KƖ,gh*1ࣷ` $(;k^bbObY@gmGep/)N={0-"PH50cqDA( *wJ-nL*Mmz \Xpc+p܊݁!Uz8*tn!M۩*`''G&2!'J*^Hz, VijyiA;ԝrWxe1̘l0d vTq|H1ę K)UmW>iDtvyTC~Ul6}n }:%0[C^ي 6<`&# 4:Ruq䫷7R3|IƛPl2,v 6Q^WK:,fмP+ ;'eY7)ZUUߚҔԢEhODb d"ƒm 9 -.-n*T K Q]=@KFΫ@i{e)/$\-o 'B|ɷʚS>ӦHQWD"0taPriR ?&Q@C~w00|il}We\[o~[MhOcZ|{-uO|c|~'5(__ch?Nq:z1™2~'I͔͗.9 ˿ڽDA?EJ69MBQ xw2}^4o~E]GF{Szn N_ʾvooD=ԫ @yo뫠's98{翔j cKE2'`G%tEXtdate:create2021-09-11T08:22:02+00:00}

PyRAF ECL Support

Overview

PyRAF can now support the error handling constructs of IRAF's ECL scripting language. In addition to PyRAF's classic Python exception handling, PyRAF's ECL support enables certain errors to be trapped and to cause exception handling statements to execute.

PyRAF's ECL captures the following errors:

  • System exceptions (FPE, segfault, etc) thrown by compiled tasks (i.e. SPP or C)
  • error() calls from compiled tasks
  • error() calls from CL scripts
  • division by zero in CL scripts

Activating ECL Support

During it's introduction, PyRAF's ECL support is optional and is only activated by one of the following means:

Activation Method

Description

pyraf -e

Use command line switch -e when invoking pyraf.

pyaf –ecl

Use the verbose switch, --ecl, when invoking pyraf

epyraf

Link pyraf to epyraf and run as epyraf.

setenv PYRAF_USE_ECL 1

Set the environment variable PYRAF_USE_ECL to 1



In the absence of the above methods, PyRAF runs without ECL support.

New ECL Keywords

PyRAF's ECL support uses the following words as keywords, i.e. words which are part of ECL and can no longer be used as program identifiers (i.e. variable, task, or procedure names):

  • iferr
  • ifnoerr
  • then

ECL Grammar Extensions

PyRAF's support for ECL includes two new symmetric statements, iferr and ifnoerr. iferr is used to describe what should be done when an error occurs in a group of guarded statements, and ifnoerr is used to emphasize what should be done when an error does not occur in a group of guarded statements. A “guarded statement” is essentially a block of ordinary CL statements for which errors should be trapped. An “except action” are the statement(s) which should be executed when an error occurs (iferr) or does not occur (ifnoerr). An “else action” are the statement(s) which should be executed when an error occurs. Below is the section of the PyRAF grammar which describes ECL iferr statements; IFERR, IFNOERR, THEN, and ELSE denote keyword literals:

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

A compound statement can be a single statement or block of statements.

ECL Syntax Examples

The simplest form of ECL error statement is a block of guarded statements followed by a single handler statement which should execute when one or more of the guarded statements fail. The then keyword is optional in this form. A curious property of ECL error handling is that all guarded statements execute, even those following the first failed statement. This contrasts sharply with Python's exception handling model which performs it's traceback immediately following the first error.

iferr {
<guarded statements>
} <error-handler statement>


iferr {
<guarded statements>
} then
<error-handler statement>

When a block of error handler statements is desired, the then keyword should be used to be compatible with ECL. An optional else clause may be used to specify what to do when the guarded statements all succeed; either the then clause or the else clause is executed, but never both.

iferr {
<guarded statements>
} then {
<error-handler statements>
} else {
<non-error-handler statements>
}

There is a symmetric form of iferr which uses the keyword ifnoerr. It is perhaps most useful when one doesn't want to specify anything to handle an error, but only specify what to do when the guarded statements succeed. ifnoerr is effectively iferr with the error-handling and success-handling statements reversed.

ifnoerr {
<guarded statements>
} then {
<non-error statements>
} 



ifnoerr {
<guarded statements>
} then {
<non-error statements>
} else {
<error handling statements>
}

ECL Pseudo Variables

PyRAF in ECL mode defines the following pseudo variables which are associated with each task object:

Variable

Description

$errno

The numerical value associated with the last error.

$errtask

The task which caused the error.

$errmsg

The text message associated with the last error.

$err_dzvalue

The result value of a division by zero.



Since pseudo variables are associated with a task object, they have several properties:

  1. They are persistent, i.e. not cleared until a task is re-run, and only then based on erract's clear field. They are however overwritten with each new error.

  2. They can be accessed in CL code as written in the table above.

  3. They can be accessed from the command line using DOLLAR notation after a traceback has occurred:

    --> iraf.failed_task.DOLLARerrno

    57

    --> iraf.failed_task.DOLLARerrmsg

    'becuz something went wrong...'

    --> iraf.failed_task_caller.DOLLARerrtask

    failed_task

  4. They are not re-entrant – i.e., recursive procedures using them are only referring to a single storage location and will interfere with one another, only recording the last error.

ECL Functions

PyRAF in ECL mode defines the following error handling functions which are analogous to the pseudo variables and easier to use.

Function

Description

error()

Forces a CL error state, generally raising a traceback.

errno()

Returns the numerical value associated with the last error.

errmsg()

Returns the message associated with the last error.

errtask()

Returns the task associated with the last error.

Division By Zero

PyRAF's ECL mode now traps division by zero and either raises and ECL exception or returns the default result value contained in the variable $err_dzvalue.

So in ECL, the following guarded code:

iferr {
$err_dzvalue = 33
print 1/0
}

Outputs:

Warning on line 6 of 'nested5':  divide by zero - using $err_dzvalue = 33
33

While un-guarded code such as:

$err_dzvalue = 33
print 1/0

Generates a traceback and outputs:

ERROR (1): divide by zero
   'print 1/0'
      line 4: /home/jmiller/nested5.cl
Traceback (innermost last):
  File "<CL script CL1>", line 8, in <module>
IrafError: ERROR: divide by zero
                                                                                                                                                                                

Controlling ECL Behavior using erract

PyRAF's ECL mode behavior is controlled by a multi-field environment variable named erract. erract is set in PyRAF as a string containing one or more field modifiers.

--> show erract
abort trace flpr clear full ecl

Multiple fields may be changed with a single “set” command, and not all fields need be specified. Fields not mentioned in a set statement retain their old values.

--> set erract="noflpr noclear"

--> show erract
abort trace noflpr noclear full ecl

Erract Field

Description

abort / noabort

Outside an iferr block, a failed task results in an immediate error. Inside an iferr block, a failed task causes an error as soon as the iferr guarded block is exited. Set to noabort and errors won't stop execution regardless of iferr usage.

trace / notrace

Print traceback information to stderr, or don't print.

flpr / noflpr

Flush the process cache on error, or don't flush.

clear / noclear

Clear the error pseudo variables for a task before running it, or retain the old error values which may or may not be overwritten.

full / nofull

Print traceback information on the entire procedure call chain, or only on the innermost CL procedure.

ecl  / noecl

Use the new ECL error handling, or use classic PyRAF/Python exception handling inside iferr and ifnoerr blocks. Setting noecl causes an error to raise an immediate exception and give a Python traceback.

This is a runtime control. It does not affect ECL compilation which can only be activated at system startup.



././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/docs/epar.png0000644000175000017500000016265614203121554013765 0ustar00olesolesPNG  IHDR:?sBIT|dtEXtSoftwaregnome-screenshot> IDATxwxUڀ{RBG @U:uuE|ݵ`/޻ %H!@ ޿rgμ$ONI$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Z0Q@q(~mI$I$I*XA  $v 1 5*ՀD$I$I1Xlk$HeHʕKN-˔-[hѢaa:$I$IA R7mڸcy3,Z)`9Pm,/yzP$I$I0Yfkov~@ZnrFR+W.7_ԴYz 7!{III&55}3$I$IO  Ad"D)Bb dzsզMvyE@F5/kx#hsٵD>R?zov zTs?Dvwt㷷sI=G#hO*Hޟ͖<Գpt1dfSϒ| $'-Wc*̏̚#;pMWrRtA$e6. :ZOAso'0v}R1q:{v!a:jΒ+V-juc: m%5q0.8HOECǗ\~$ҥ߹4~U"""ԡLO@raRjOjYgsw[9bNr Hj֭\ʭuxh=pugFqFTiIܲKֳ3 H`p\[8n"E)$H=ɻ#ThЮ Us`TmJ*m!?1Dwx8B~ ?y0{,t?S~fP/b2/q<;ȸ[<,3z$]}<ē/r5WL:'}^6cCI_D]\s]2'˥3xŏ:{Vē~黀u2nDŽ\ [;*~ۓ eGnKI+OQ/JyH*GP㵼}m[䡧*+ҟ^EqԢ TDVĆ`v$ϙ̔=~L6>㵞Ds5'c%]KU$j Ƞ5ثqϯOrs\ʹ[þƜWlmzCZ$5qo?G9"}ŧg .Q r9gұr8qs\U&~ ]?_N`Uؐ@*5ݰ-9gvʒ3{.ټTG{k/|4~_5;"(_>=q=~j<@xkڶΥ'-]ÊUI(^ZjѤi \G Y7ΔE9uP#3Z]ዟl5+Vm`;%(Wwd/r~y2s>+W!L3jOMqر7QQTGl~ ^H߷$Iٳ{@3 2ⷳ{gQ5;@`/Qҧ^,.C*r 6U*J2kL(I-84XYє U,u>c'g`P5k]!y.%.>p*Q .eucz9#r)G?OfSYw#xI`n[㿷Ldӆٵ雖3c =*>%YrgXu=]*#)ʪ__ѷ&.)KU ٴj!S'O㲻n.86N)#_f%-'K q3>WgA|M|a~Ϙ̘q }rЃK*>}+>#j'D\3+3Gظ{;וàs#C} s>+o~]%I#.qu2\w${W)YeMY%yӂ}3TDz I)ګq:8'/“!˅7MFܻ3ْL:!3{vgi6qGpX;$RnsZ-MšL_G)l1?oSwBy%䔬 SxoWWELuE$urD6id`˥}Z>y_)V{+3qXzqRjLg]GVuP$IR] *'HT~lƙ$vl^o>yNJwRP5'/˘E{n ИV3{hyozE/Y Pg,ڊ ^ߛA732 Ѵ3i} Fc?*ݮ+ZQ6扯qْ*r<pf$&86c^V|4w~Xz*\%8>fֶ"}.}C"?WNW'`0H SܕZ~xJpKWd<.֖!ןGL >,IROWB.W<l|}{<dPq^Yz^q1]DҦ[~E^@˰éթu>\ q:ВJJ7.oWq Y<3ϋͲdQ?~az e9KVA؜l\ý&55@xx^k];37&һ\k3UAxRq@D/HN-h8FQ@$h %@J024;q_gdHσ~ ]::}XW\EB9T%xә4}. lb˶$$&ؽݐ#ɗuPݜJQF?F?-55guF.]Q̎˧ֵGޡ)wu9횫rm6.s;)g_ê}פn59Ovz-Cv0Q$]kYWZeи>{_|סԳ[2ՙ$K& | DK?H7cȎ8 r DFFO컖 AJJ1?yF?ӸS#*AdTVumRa֣ghlc$UƤtD`nԠ/HlFܭ4' {vcGrqHJ=){:_s^?ڳ;}#ERFJ9~%ւm7׳'gRRIw(QJD-}d}~3=!a/HI%`%ܮή=|?%sXwRRIͭ+q}:xB9+އtgዑ4w wΒm9},^Stbҷ$I!6aOjwr3K!'1Dttp$/^ug`eڅz}2>m. OK_*ޘs=q$[_k{G|05W=خi(RƱy^/\=arAx-gN<Q3n c˥˶kw_6sj7*Vv1XPcrprU. {d#4ym<*GS#Vۖd4<쭬X^DO@p*U!:VVt 4x {\}_i<6^l i؃!$^#L6Bk.'t{@$)|X۶ %&xi0BIS=cҿ&f~OK.Ң sNUxScm2A X2=ZסLu4oYOw}̒ {7u.Y8n?NQ&uj`QXϽJR& I^AX/\^D]̮|h;wNMʄm]ƌ+~b37n\36*u1fd.K/E,Z܃HagØ1ˢ>^sbCUۉo3x`ǔ׹_JcLlͽts׬{x?U|9 =lg|_-aһy{C4;+P&b7kOERv:D*U{d8޵ZB"Exٲ%XO䡓%IvЅvofbҳUi& ;BVj_XqԮۄ^gpٻQk7?ˆ|)O5/b ]J+G hݺeطL&]UxXT^A{GW/~1Yb#{KGSAKN;TbWe!珸;xə\1|T^˚-IRڵN~9AdziL+J>s!gf*MB,YwV֡un4/r?}u8l<ԓnMaf'kP9 #q5Rk,DܶH t Tلgd?ឈ.IIMM%~Vε̞ݻD-Oxxo/tXp޸c$IǙ |t|C $I?13Ir\2Wg/羘q:N$I$IY<9 Vb̨#"j$4ZPNpNc¢a|. ũ ٽa)GλC˷ѥbo?`=)V%I$I#vJ |#nnzr 9;iـYK[1}ZyK],j*;œwGsAq'm'I$I?#HU-]4n=ȸx(.} z=2MT#lP>\6?Q?I$I$(,.>ة7r]i"f;xb< 8x}b"r}YDuO' X|5CLǘQ/}:ѪYsv}loyo01M[炬F|%ӿk[6u>tv g1ŧuMԗwصG^}(t]XZH>gq魏]bI$I$eS IQyIq%0vSѱ4ܚ2{1wH^~4<ܛ_e @39q}m=)'Gf+מҲc'z7g ]LׇI%K4?k.7/dyfuiw3jECĽeԽ\x݇,OblEªL?;?>O&f?~6}j Ѳm"+c2wYVޢTmlj'Whv6_ǶqvRV$I$I >T~äc&`x+yx/<{j0PE *xX37c2289Ծ< Y$żsͅO{Z*<,BFM.JÊl2L"pbcʕl^$#a:fOYCo󗫰\ӿ/i͞`̩Lu_Jd9N0ଓרY1y [hrvtZ2GS lݼD =EZئu%tn߀rWkK$I$DRAȏ ֓x*$+2a[i3k`cA&FRr@8 ۲rMtȉx^=fƳqCd+qlܘ DQFT5k ڸp`r5C SII$<㟳p%pwԍmMxib&Q$I$Ub[*s>,W!夎=g5pNB˜'7?/fl!Rf<4iR/D@JX qQ Dg\K7G2`h~5 2mO ^y]] bII$I?NN8KVjep)!Z=zPŬ9_;C^6a|D!fůcn ǪM[ DQʑ:@*0/5k⁜S׬a=@!5D:ZE@ڮ$޽%fҏ$I$IҟY! )'uċ~ VQ:v@0cp^M/E!m6/1r){ysϽ~!W Oo|y|3(ږvYlD)9bm׌0/٘z<| @qwޅM(`b!0$I$I~gN*!T<\P+/s߳:1[,;W_^V傇ci}/^?&mL);˿\;҈7'6iI|&yKKyי UϼX^UYM3/(H"[edVe7kѐ62u]ϕ5s ひTmcPI$I$p_Iql(چ[_7/?֦U&T+X>$RmXһ)xգq."7vpl\%[H"@T!i,~=f7{P.NIm0oH*ҫW5zo5է|p(ו|JǷOfu23QՋ"a &ZObx4zCwb4=:劦cN[F|=o]Z$I$8U|K/Z0lj̝33#)[2UŗL~z3_u0qZ2) S)V E.ӛQj't~)^7~Xu~хp4)|8o~O3\SK{ǟ cęh/Q4{ ٬\R7I2k1cHVF)WrŃ9Yţz$I$I]ʛ  [h"bB%|:6 >)I$I$eS9Çs=tƻT$I$IRH&%I$I$dP$I$IRH)HQsu$I$I+)䜔3%I$I$dP$I$IRH&%I$I$dP$I$IRH&%I$I$dP$I$IRHY >X!I$I$8:,\p1E$I$Iq &&30e˒$I$IB2y(I$I$)$$I$IB2y(I$I$)$$I$IB2y(I$I$)$$I$IB2y(I$I$)$$I$IB2y(I$I$)$$I$IB2y(I$I$)$$I$IB2y(I$I$)$$I$IB(@ xKD+MŚ im _6r2c їhzt{sKʬ>2eJ6mIrDI*To@vwmZ xR.S:14oד{Ѩ\s?KQj=u k"yB3y}t-%I$INf*Jt. M+Xl*-_qzW~=a\)ϦUჩ˜09> % WIwmJ?xvSg?7fYb|1{+c#ܠ JrX-Jp1K$I$}<@{,Sv-;CU8(nϿ|!qkVi{X;z9ޟ=OIt9/Hci|3)Ŭ{᷵dsOX ^q3_s}\DՐ([yq1K$I$}GmSsxvIGg-|9Ju޹ӳ&JP9/y-F8ηT oʳ3RTxKJ)G3~w#Y$I$[5ݜj+} ^NMv͉m֚N}eږ\K6{3C}˦4kՑk|+Sꆟ bbr3S?R81Z5y./ޞGڨiCG^7r]i"f;xbļ# )W?4)(K/=yN{1` yY<6mlݖN,I$ITj $I+xv B{ѻs3$.w3o䛍9{\>`JӸsN҄'x}C#=Z>ZI}׷XJ}81Y qFL@&sR{' 1s#i08|;'TqhU#ytO꜎i鯣XڞY5^ݼ+K3$N`%&dܟPba,I$IT iP+SR~Kѹ;d9m6e 㞼+OBOby䪇msbn9jٽj|׸/r0-.~Fg90b9»<ЫjF{ ,|.|xI@$M牫`즲t%ڒ]nG.w?³9  ڎ&hE'{d -FW/GR76>#}bo+ΚXDAT;%I$I QH{Jf|E*PAt7N=)Dd|뭜>D׌:[>ץ<IT ddNt5468ő͹WC4VcZʼnr7֏ghrW/1lUeN.my*]CovyHEPakZ=dW6%m[ w$u|yہ\qٲ9CL%\'\:^;;rZ̗hBY$I$(an p*wgoh%.92FN$95bm`ro8n )vDrtrI,z6ݯ?˅rb6u>NSR*GKu8_cY0/L3d‰mV+W5{ըԏ2|X^w!HOGBߟ5prwgVTnЄj%B_-V+/CsqИ%I$IBJ%ykjaD-E iݭ?֥D~t77=wnv;v %S'*amƝwޔy $oLqlڔ Tzt6EW#@f֮M"8y 'ޥ2+Gn>mF'e(cRe35떲hM<ΛP5X~Mv̒$I$IG_!%+޷`I%ʴǯe`jTxpA=ybC=;|g4CŋhP`cMz,6JG8ZB<=P^tm4m?-d͜9lY>.<8oK5AϽ0!K'?vӫp'P)*ڀ _|zTd۸7Xx1M&@*c]9j ];1ũ}3Dq[ Y0 M.:ټp;H9Hv΂'T炡'SuԽ Qyd(nI\״<'لru}( ,}%F)%I$I 1_«L}ͣ))2+:óWx'+.}qo\N/Ѧ Tg%,}=MGLd]{]¯~z!֫ӤaT λ>/vet/̄ߋp=QDFFXhn}߬~z~[VmPt{o`y,ޒH>SĐ@<3 ^7wWG(+z{2P@.oܒi$'bƕ,_+=i[&'_MW \O<'^|g\0އsku3ыY$I$!5yWvaQэs5Պ6k_7d/S;f {#PZ-9: z=yC.K׮$b P<'+/(Cf=rқT+zg-[|DNɑXONo8;ǿ_ 2uJYNrD)*hA=ٜ[s鎤.e XҔ.[6м}ON?1s^CoȻW`/Ч9TlZ<+z*Y1K$I$ `…q,b rI\N=3O qij$I$It ?{$oX]6L`7ȗ[쩜ġ$I$I?]#iԲu+RM+^ u@ xK6")^TNL,d^4*ߧ8b9E@ˍp\Ue\gZfi64W9ќqVp$"Ÿ .~>>{s^{)K?9ϗ-{qbbgoc17*WF&Mq/h=4x񒇖v(_;~}Q®8e J8= >ϭb, cS(E]K=&n-~d鐚ؘ,XT"[]9 ٽaӾug2.f}H ,r;St)rƥt Ե'"""""""//yhSaOh {>]^go`c(V!xG;!xjf#Nq' }_J2N^VϞĹl.CgKrA0naq#>T$y0[,÷=yxC1k,PUu0 wU]mpҞsxMR6%8{~lauDU_@^'gӏjy(yÝ l;oe%O7?a5Q 7^Uә#g:;}:ӹ?b3? w6q0܆6]Kuv`i`+؞%2h#) IDAT""""""""ϖ[N:}ڳ%[5Oɇ32M4>aˬkC+ V=BM'>5QEi |8^8iIv`qK&4LŞ9xBʑϨqk9v3kP-Բu~:|8:MRR9ʺbd73ݹDDDDDDDD"%ȏG ^`i^ލ7?>v(A*$P!a,7УB_[՘4%JU]#يM7 {0.i[nr`!03'&8gӢJ7ڳ3u_a“dJy{8r 7nGD ;ÙkP@N\m'=t4taKi~?qrg|:{t.mlʆCXb  ۭ ߭lK=~lھ}8|ؾdW}<{6)+"""""""P0(Mq+ Hnܾ'מwS&ðmmKQ׊T-pw0I!b(ˀ9?ӹXT~/Ghh(za!OFl(ќnΙ˙<;V b㨵i,5gEDDDDDDD2D;+2=郗 s#f0]=@\b o+;wa ,ܷJɋV+OzҲao?SWJZ[;՝W*[/#{ZRƵl8ŀp3b5NT` d?!Enʄi`4C׳~XjxFyF<J.cNAS+z9w>9proJWtb#αm7S[ݭJe@>[G8fԍRF\ϵ9xИ2՛޷xzt b'-l`-rTNgcE`-_s<+uDz EAĉCܾ̻ͩzڟin $v.I0=a2y{q7_VZ~Lg>2lg藺o seS%(Na꧆cTiVAWQ"""""""Q1+^+i_\y%:d#G.j2<DxĈ/넳M2V'cd9=q4D1tC|pk:uǪ$H!xҒnIʦ$=,۝v.=Iyt;+{_~61C""""""""N-#/firx>9-4,߃y880wS}Fb_,e;:gi7'USJZX|֕aӭg,~Ic_ f^LZ޽ۡŜ=W͍h '%->9 n\9ǙKXѧJN.y["rI +N,Q;8nS9]5?U4PDDDDDDD^@JW?/oa;clYwNd>_i 8Ӽb6`;q׊4v=Oϲ&ü_Wn=bۇ&zƬz*O]u[xg̅}4n^mՖSrh'!d -De(WZ5%_J#tч7^ ?p G8y8AMХzȋ p\\\\<PDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD RPDDDDDDDDD |ŋgf=DDDDDDDDD$Cǎ3"""""""""*UÇzlYDDDDDDDDD䡈䡈䡈䡈䡈䡈dJ* v k.ϊ͗^痱ayR*yڃIִxb:vht9tmiZ\qsBg/9|̮c'"""Rxon..TSXrqjfǯXp8R2U2OglNcEepIfsWyT$sĬw~lۇS)듡rlևt*3EJ.}n\:͡Mٻi)x[̮ĜKs r@ąƹ=P,Ǐ<ư! JYp)mY-0{{L5L.^YgV1co̱)P%Y`Ln?tHME<^xXYM%PٜM2QtAX=;ohceR:ڼ.Fkz}Z\~NΉOɹKUojѐظ=kfa81&..4- ]\pi#m2m_;ϛdv1mWшĉxrWeРV& wF}!ɼ \/%tθ0λ-jRɭ,j4 3]FO;*ۧ [Zx+d^ j[92߶EyYz6(gzF=WHJWCw?M6qOk9I:YgZE9D%d)4NԨTey=ٓ]Þ:}ڳ%Khܿ =pZF~lU`?UI86f7Cܝ֭^{ kuqچr>airs$0B|Q < ][$Ru=B|տ >߇kީKNd3e*^4]ƾO⹴1w6@ (錇QdU\uk|4uzhCo:{@aˬkC+ =B33kF!1? VMel~ĜO:3zl~[#g~kohI}םv0t.k (Z__9c+}By,j2n&VזOeIH,V1yp79_A&Yy"Ei{ߋ 3Ѽ+ĆgӢ)o5rf776|7?'`i77?&^T}u?aZ"똳KIDMd^O-[k9 nxڱKw6=Hge+9fKOC5n5.wLQv->y,Κ:}G=I0oe=x٥ep:3B+DDDD$/66F ^>`S/'lp1n%Ad(TpPX.C!hX$2tJXܻ񣗈Jˌ٫t~֒n <hQ=UKg<̏~g@Y#9ָ/Y[(;"Ud#8.;ƵNx,`~K!р=Edcxi3XPأ9< &3;3yw:Ĭ/QkXj>BDqwo[IPDrS?i:|m|71Bވф֍㿶2;>yˇ@!Z7>4{X2kzow')gz-ԐHB _>|ᙙ=Ƒ⤽q>?8Z&W.rOKZL!['6$ml7y$>6 1ƨ\p&ϩ3f7y=ЧL1aږZ!^m[`LSX~+ s;dp`e@8W.GއS5<|, WsAu>p(tZ6d,KtL*$ha\l\VJ_ݟ)Z+Ap]}cY{T7}p}nrdx#zl6/nAWZ_DAnO-޽D+xeOͽ]:w{Z0^8](s\۵t* ,)Z6b@7pq |PťXJ{&,#M"cܹ  d237IN&-bZ'd[ږ,)Z 6b@a5K8{KqtJ_/Alˠ=<@ !cȺL0DEofwz݊FsTUK`jlT%s9RS@Is8u?+lޜP\ +́1$)g]10[A]GGz EAǤ"ahEm7%+Vsj|wǀCsZWO}]Vɩdz.E3/$7ͽS39 0sUC&|w38f1mrR0TqNvI~gsU3˛\ydt{)p6 5~yѳccA/ֽBDDDD2PaccTpÌ$>Ū51 \Q4 qѢ!5bݬYc` XXW#Y4yx]k' W{qùt$G]!Naae؁+zAz' ӳG~@\iY2M;y6┖gU;xʼn% cgǭ9{* +&JL;D\9Mϧz9'W{14Jns;EjL}J]FN^߱{xnWKU{Kn]8ʑ3F: IDAT7r^3YAXMvB2/a1t8\_ռܹDDa}=s%69O8ҳ&}tS1,+T_ˇս0=FHD Hӏ.i>yɘ9z1i!x _o3 obsG[\9w 7|-i{z""""ڶdĨ뷊e4ÿr ܝ ;/;gͷ/ŁC3e)[ܙ|1ܼt={OnfG=xQ&~f͈ q3F%^Կ9=Kv華(yՠ)ظKGi+<ؗup$ N|طU[g]޽V/@yG#p]mEy1e9IZUjuB OQ,8v؎1}?͋_YPe1ab)7o?pRLgpaȋCԭф]+ԁ뷿f1;JnT`2rǯ۴[֫SeZĤkr0kUUϞM;9u*EMЭ5 e' }ߊg%]>9\:_زx.mvï]6Z`LB2mmr~=g]PYװ >Fe. UYftޞJ͟FtO^6Ef'o.d麭 <3D[¾Py7Kmijdgރ^{d4|غ1qb6GPص6g .LoKi1w0ܸkE^G\~Hw>޴S1#m~\mt ۢڙt+Ւ'̀>>>;vhpRJqĉdŋkχ;Fѩ\N:"5vELJC+-\{ c{%1D XTRy;蜇"yk eƄV8Ņ}Ҋ"""""""b:efl?fZH$GG=-^RDDDDDDDi<yYPA4zȋFC$w{foٵ@sAJAJAJAJAJAqYɂKVkZDDDD^f%+0jlرc&WGDDDDDDDDD2SR033ve1HC1HC1HC1HC1HC1HC12JʬzH8yرcGN8u,D-AJAJAJAJAJAJAJAJAJAJAJAJAJAx̬d1>|xfCDDDDDDDDDL2ޏ󄖉7?~<L{m7-Z$9IPDM'"@IN-ؿUPHKfWAD$Ez*HױcG/^lpa|JAϞ=3 ""dvDDDDD$ Xrǎ9qDgtD^Dfffev5DDDDDDDD?aǗ<y $^ >L"""""""""/ C)4PCQ4) loYcӎ{FS#/:W.`麭>հ[D[ι$nym[$eى܊rJqqG=Zz5t>gL_rs ?xg^fFDDD^b)-~+/k䡈}n}!R |G߶h9fwLXq*WիSjEq$׽ݰicUs=$#f5]\paof%^RJInSFAC`UidyqW 1xH/pw޳Y?Sܡ&6&(Ϳŀ˝ཬ=s8]·dLUڊ_'<|]Q;,Kz1rÛ)jhsgF4|kM962Oz?%tڊŪU >i0| ==.7?qRPg|ӓuf-?0r%etsϛ\۾Uغ\%,ʒ<)_=>D%{Cspe,i)[_$"֡1)/vӧ=NEKVQ^jJDGZϼ׾S\νlkH hֵ7mJ]dG۶H0/Kţ^#=eS>_![BX4s7#S)~/˵y]zeNߗ<8z+h5Yr],8RrCIy1KLi1m1!k1qb6\״vOj[NNjOЖ)T,d9~^+bw0/gLwşr,KF ˶)Y}Gi(6ghqX8 WM gknmf|l-{>lGN'WzV$_˴X{iON?r5\=\tCV""9ُY6)ʦ^^-kڵqͨ croI|i-gr:8=ژ۝{+:3*&>vΐ c|&4w2A1Զ3m1.=RL UEu석c\u(1NJaռ8uS\؞2/񩹉b2k:Ps ~}p&Cx.]])""HPr{feݵ,%X˱A]jy@/@rmjџr&g9C;᫁8sώz#f3s+kX5K*m$HԷ3Um1+1tb41Mw]'$󚊈<ҹڲoz8ړwf3G7Y>j~6nw0ߙfxH|ps:㭶xa|ށ\2q n޼]_9c+}I@`PA_w0_0vpR)]*ӨCy ,,;;cKOY#b Mn0i?g1f!@6^k5l j712m;g^>`S/'lp1n%3bȳ]#w9(RgmJ%P^ hs\%v+ܸEtLAp6 gAQsV&$ יt40Wb\?w뉋.D +x#'9q-[dӁUJ/DDD4by{39/&‪Zd{ZPU5$x7Lδ[PIB4+I楙ѴTHDDD$ӥ/yXGs̙ !g=%}E%sܾ\8dh"rptrČ`Ǹ4̩ 8I;09x[RԵ"Uk5īm IM^3 y4=et=lnGO\SÅBɑ3B֮ .PC?8 5?K) F8@ki s}X˯tar"""Β2`q.sq9%M̋_L2t3M 826.k*"""/Aq4Vn= O:?Y~z6eSo>Zug<sb8$}pPǚMLK??jU.߇rI#"""EsS&4Ik|3oSr #"IC^*e I{sFO{47ĉt{)p~ o?LQ6UbY9oԽѓ(~Cҗ9;=0]+IMDEx$ے~~ěNjͱtDDDD$seqٴ/ ԢC1rR(U;Բ[&2*OE4w3(?ӫ?3}}BTboqzB~_\f?䑓/넳q|0ڇz1t>U&UbwFuiI_q$eSKdNk 93p3גnu~+sCjIT- |nmAeRs2#Δb|?s7~Gg|͜zhYDDD_YeQ}7;WN^Ήl@dt3Ňoȑ8pԄ]='wX6tǟ J4yu%5z gh5+s޸̙#]¡z6- !L}ɲ-L>n^:ʞ 7T"Xc9z1i!x _o3 obsG[\9w 7|86&*@(GvpNX##qg.EhD7>ۙyȍVupĥ釴 -b0uQ/Y۾ Yn+{q: і/TҼm[ڒx_z]B$ܒ9_ᕼ԰ ԣUj#yi 1f;:LTL8MqdUB:wD&პ/b%c|غ1qb6GPص6g .̐Ƌd83ÇsLDi`}>}S̮\\\_իq,)" _6_DDDDt\\\ۛ'N.zY*xTVVtx0gXXgLqPv—z8IN/j/""I]~&O[EiK Ĭ .>캤E|ڥa3u̮ϭbW:|#o<ĕ8[V d݂۶-l ˂5NSzuWH"w1mlj74>~xMy[R니HKr1ƈwW!ZBhrۖRJڥsdoMR]C|=עPrm1?&uGpcQ φ9ǙC+W (Cj3>{>c\Մ`Buo X5KQz 8}\4}|U2^N}3K'׬ofޗ;zp*˾i]-W: pL5\] ߿1Mfu!X-eR?:rHEly^]'sʼ$Ne+a{%cM D^N^`ŘxKL5g,#pv,LY? /p3ƂbnRw2^z:&XqJR.}8a]٪xT"_<6wR4s e[ֵ;5"9،NT{{ܸvcs'M>MM[R니HK"yx@ \*X8ܟ&?q=3^1O歖ca{"k~Y8w 8PˉCSrT,7kKM[2*xmnGyC#kC`qb0MѨ-Ij%ѹci g߱>$U=nUhZx|Ɂ? N%s"jUH|@4˯%'&7##`ƻ5eCO.=)nܼ CU Ʊ]Y83`Ld8LݴS3iڤ-5H/ԳlIx[Ӣm=rdveSY~5{dJM[20t<$C[\ (^4͍Q@a8ݡh8uWʳ33—}Gb Q|X .t,+ >-H"p$x:^} I-h8) EMtn޼yP>2gflȟ@JgesJXۤ c(XZ>v 6y{DpN20;<ѣg}7{8``Aq"5rJ2bL+5+p n@ϩ$>?6i{1E2+NZ5ǵPboA-5mX_DDDOC|Oߊ!)^)+H/ԿVtn0ͫ=:g 7M]2Jp옘E͂ZLC*TV=oeHJ䢜Siv+Ǐs]eS&<(Tz+FHN`|֘"UrHaR5۲"wo7r6sFN\oK"""nOZbk<'qKPp|(TJ**dA7r T_Ր!I#14|vq&͡`Ϧ #&!! = eKa\/m*ӸqJgx1{lxŦrj\LϢlcÚ]<TK6-`:cLU/]–7!!QP!~Ðk֘"rBRbP\ frk ֜l1\ ~x?b,=V~qNriKF"""^nTeq|sSfr;k0榭9nq߬x&OxrULw facoEDŸ ~sy=fn?̨^*ë| N;Oql Pr mIr]9DcE\HsO4IEȺ|G8P=#[t@^ O@qjNer@7m10~74L31Ld@m{ck aʆ;WqOe ۝Ż  ̳4}!Lĸ8v,-V,bLdɤ]xi}a._uGۧ2q5^!a\0EgP…MtRxur$0?-Vq +6q+V$2d cǮ䦁~`LR|x1] oS24~MH QI(T2oŴ0mfT(ܸ7ES0D=ލi" ĸ^ ,\QRC8a.񴞇8 ٵfx/߭&rs?NmgV/4K$SjKj[fxϹ>iM_^Ў&40 (۝8xbW<)B~(_;C5ch>j>MM[R니HfJ2yj'_Μmkրy^ܬLW~\?jGq.kߤv׮tc|ˊ|?×vIk9)PM2d.Ma#HxT 8X)N0LpmjM3vJjkd˵Gx:ݺـ  ̯[违ÑpzM}6-{UM龈Hژ݊oפoYe7.ry"sŮ եYvT =)-cB#8gS Q-[16 Uvn:Ů-'r(C`Gug`!#J:k%ּ\/~N˿ ?`b Rf1Valgʁ2>}h?dvD A &hobS}г+͝b탤ijڒX_DDD2P{ѣ9}tB! Ǩ5q7sdv3e_w,۱oi@p>oX޵+.}FoOmΝ'!"bPHHƟIOr0v Gd!66Kz̐]%I#ތ53g$YחQF`ddT؛ĜO9tBb;d2Ɓ9xvFBp&h\vG^Jb"øsqaX&6MiS'$m$""""!aɐt1`vk V;Kz/vAT M 60}9lNkˋHϛ)Sљq9tOr턡ɓz2TvDDDD$S$/yX7#sYr秨{{>1VY]XlZ0pdJm b_=<6Ҷ]޴rNz%-K"""")<|4‰SRU~>!c#"""3ǖ 28K""""$kCy(y("""""""""ÖE($$$ """""""<IuìHӰe1HC1HC1HC1HC1HC1HC1HC1HC1HC1(cEy]GT|3Y]Lspt,Ko8rQޚrtYLCxt2>(ps3i8!G&6s+ftղESzϏ8gDZnT(D T^H("""]%/yql]آ@W9:յ$0n85Fs/~jI]< Ō21b.p)@GGkzs("Y!Md֓(K<_fI,=UId%LfӋr&]W1Nv,õHfo/jb}#7wuy#fiR\3(17?f0pdVW5P6G0bp3~-/=EFRױ,;εXl5;v,9݌葲$Ib9%xǃ6Ml2JƴC䡥ҸLy}8xjD]H%Es~W8&x[2q߯fVW&vNN5-/=>%#p7{7cW>YѩS\ɺꉈX <ٿU!_p>uGό_vp=' Ie۸øg=ܸ\Xۿ˻-ݷ3Lֶܛ~ Gjua}jRof?Er:KN~F dqWnb})7:aÿǥloB!"6)YM]J~Xl^J S uޔL9ilj>3j:si _MǦm sSX -{Yx`3cL5σ ̟'|1)ր}ж 6j$9߉[KRsn-+p_Q>5ϐu*g\ɮ+<4X:ts>K՘͑^X)]R 87MrX/VmELk`px}KvEDDD^c~;VBKfvP-ADDy/bH~^JP3wt;g ?lҎ")^, é#GY_ʖGu2KTW9vx @,r""x9?f,(_֏CwKz ͫVh{v8.It ,4jԨ>n8 KFE;1a&ZgΈ4\ٞC(D>(~f|'_'uG{p?VN]F8$c[~_e#L+[*V]3b&N^ʔfJ &CrIlyi۫΋ceJ4$=שb3 K"f {9`SzJJ.ڐm@t61W3Պ GjB0g{JdUQW1r?1AM|H^?:P 9!17ޫ&[2soVYbFQg,Z׬CIXɃ'3}vPz:~6miOMkǙ-0k;-P˟_{~.ː.z$\0Op3;ɜcmO:w'se./&։m=z#l;\\;n_lda.Sr2',*e'gne@JzޯS7ZףpMXw0N/.;;S|JP).ڐm:7d Zͱqf|6Fx|#Dl`p΃'?0N_DDD$H|S: P%+X֣_gvg51)M 䲣Сm!li3j`ҩ=5g0Q~j3p?&Y7嗢n5z q)Cl.ܹ/@*%>LmQ'%x#4'NDjH5LtA7:s\͟|$MM_JayCcmddsX?cR4$wmB.$Ql*<9Za+';}'~7/'}=R~J.Js0m׆o,tU!wkykk|#Zz_)$7cX$k""""G=vךKLܩS;~TdI ;$= qp{‰r57p$U=:3G{1 63os("""9Q<$Pr1o^w"" v_F1x:&ޖmYZ>aa2pi=WwR NJFgȴ"FmJT݀VZpt7o^ PP^?XZ<ZI“I`F,[@R`ܸXQ!&Ŋ\qaw"א67oF1=FM:,RxڐRq9;c:`0vcلfc]ab匌;DQknFoݑvVm!k`cL26N~M߂ZL_)DgqeE>i3-bnбm,k̥>?<|" &&&yÌuev ;?FRFz]JdD:ޯ7$T=PT]AJv6p'Vtn0ͫ=:g 7M]Yr +u9,䡵-&dd XSw,h7vg;}.6i8ቮ˃kc6".XkϽVu0ԇݖBL (+WB;H\@.aw"q- @MBBB.h!! l)-סJ} 4|vq&͡`ϦY2.YVPDDD$C%P֨,ed$o?R>~ g#;׶cFU^Kpcb-؟V@H0gu<ٳ1@ʕKlH̑ SFf~'_Y}r%L]s@ sܝ1]ˍxa"U5d9Nbj y t/˷24R\DDD$HbDGQ ;N4@(;VlV@42d cǮf:m))v?&<91A޿s9Hacɯ\6PHnԳH|,/{${;;=1] oS24~x#=R)NEqI:G-CP5NJ6?Gb zτf$WV& ʤ(+un~7@fxϹ>iM_^Ў&40 '{o;Pr1;D۶O07xHDr\Z3v;օy{y`1Valgʁ2>}h5: >pϲ' ⼉8~'L2d.Ma#HxT 8X)%S7QTVY+u|'M|sZ=_>9RvNmYy]V׆5 ͩ:ȫgќ>}:^EބcԚ9mm29۽=_uY7쳺>"""0v Gd!6,$n.c9hݎ|CMӤjx{{3jԨdH}>wQM`o9tBb;Y6yxNMЂ(F/8Dqaܹ øa,ġ$Gf`ٕJk$aɐt1`vk +/>MisM"EDD$ypb0e*:y"nq58I0:1Nd^m3P5FADDD$C,i-"12At%#YTjְH1ŨԬ-zuVWQYr秨{{>1VY])̕䡈Hr)!б̱%&jHN` 'NOZddy("""""""""%EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDD %EDDDDDDDDĠd'NncP9GݾboT.C]GT|3Y]W Vvѱ,M=Eqrzk9:ec2]GGG4(ͤ#NHV0x=u,a }zPťeshL2#..#:Ѥ9QzC:}>󈈈H%/yql]آ@W)o%a2GdumD^!0n85Fs/~jI]< Ō21b.p$]RGa uޔWe wtı7.ezC' +Py|6Yz>:("""ȕBW}&rnLE9Rf1>i8'mY7쳺J" b#;Âizs<F ( 'ҥfPx5cnN3s%_fu$;u+Wb5b;Xguu^i3|MKvF"#QX>w{3fZ>w3a{Nb=""""ǜbC%#'m\g]LGNq%'"""JI7Yu5hI<)[ LҕfݨL -#^.t=qD`].M浨\5P/e&pu[-#X.[6 9@t|}}_4 " z>c 6pȯSѥim]pT*=hհ6pqNm5[~ |ʡ2XRgxf1sK=CNNm#<sf::uɭi_MAqPǎn̟s~Vpj x ?Lro''ʬc\.>؃_r2!z)ڸިօOL6V}(",ҡ}8 ~}rPfq7<--eR?:rHEly^]'sʼ$Ne+a{%cM D^N|\`ŘxKL5g,#pv,LY?zZڢB>韟 37s>3m{y=r`fҒ{Lzeݹuy#gd/)""""I"yx@ \* p>1 S)vZatb#G u1 ^5Dѝ{O3ZĪ˭5|=z 7ޤx/ӟa^ ]#Hd>\J.o&"9YdR쿝9s&bě]&-\t#a*7}J/u{ uH E}axZQy>[PkZv*:#o m:%ʜcmO:w'se./&IzZtîZT ?o3[`@tlߵu,x&U/T YϦ-v<-6Q4vIxms2L'|0?2./r`v45P:U-LCߟ63E p2͜@}rɿNK܊ n#O~`T|)$ a18u%J$WW/d{(V>@nj9AϾ' ,]֣_vg51T7/yp>e]w1Ns.˫`2)ҥKޤq5B 5q7})mKwbh~w^$L b![}6ppprb񒑦XuXadrt=Ϙ ~ IO=)ŘQgCc8=mi3j!Sz 9kڕFշv^N䲣Сm! K^,8al/ }1@E,^JDDDD)mnGy',ljlIx[Ӣm=rcwԶT%)sX:t`BiI|@4HDʸxW{:Ͳ٧-ks$8@$&89ŋ\UQJBtP O8/eWX'V-/% &3UOhLT|oyH43^[9^Ri=/ҴIMxFly(^ |7C7n䆁UƄ]خu,?0}tjnyg L?b8""""+aˡx`iI00!q# hQCMt7 `(Q7/y;Dm("{{JyByGgoE.igr޽6eؾ8$5ε=ci4)l_BX=?/sQ %Q L?p?pynx*ӧ!+58W@d[:>x:+kM-bb,D:4]>,+Oiqxv mc_X>g.]admy0&&s]lY70BmNi\a&|%e{m1>06N ?0>rڍ#Eȓ#Bރǟ]@f^'3zJ]EDDD2D-&UR ʕ+KD]5 EJ [ 4nbxkI#{PA^}ߴxFl)l 6iܸq"\)R flf-qUϱx,gAk:&&&^0ct-vr 0u 2*iڔo3ܾKa#ld}bj <"gQLWz[_oiehY#Kf\1O )Ҧ &_/?kbH.v<swzxKe_(}j[-3&/ a/O'xaQ ^,Μ9cYB,v?&<91A޿s97qلIx9`ךcw9t k~Ttͽ;=XSSÓH QP$+Ufq\=>h[V.}>c߻A Pei6#G0{,vS"7qOL}oDSYiSgd~Қ7i| ,U Zz%176/_';#M+H=5y/B#ڹ cSl ظ/zv]:]k$hYg!d=#'fzO<oθצ^9/vd*;~7bזX9?0#W3 U),~ +:k.̪3YsnH5=ӧO+}ޛpZw3Mf1 1~{@i8LVӻp;eݰϚJ`IkERM疈HZ9::ͨQ5"FFF5iEk|N%%9.>;'N6e;`Ӕ6u-btKCD^+4[""""[Ö0u!MYbֶ\yMÉv䉸:s'vPn}oæ4&k&GV"ɠsKDDD${K^n=G4g A5?ľ{"++pttL2L/5:6#UZdй%"""%;y9dY] BׇQtndoɚPDDDDDDDDD^?JAJAJAJAJAJAJA"ҥKguDs̙f<@/"OOϬf4lYDDDD{^U` !$Rj" bCW]]*Uׂ!D* M@ ҋ B!dKYs'ߝ#I$dP$I$IR@&%I$I$dP$I$IR@&%I$I$dP$I$IR@&%I$I$dP$I$IR@Y[pgo]SLƔ~_-QfGsl]Fe/Ns7ZYs%D9;j89_0Ŭޘ te_v6#hX gޛoA'T۸^,JبK/#>ѓ9% 1kn‹rL}j׎"=-uf #g<_+R%-[_EQx½|Z><3;I$җ<7}ǎR]Y8++x5wO۷ҿC#Gg숤 ;QU.I;.wf1zD=5ZEN eGw?5EϹm3w~%3aPFuPrO^ᔩZ]'޵n6#4B*s )쟤!+cyo.d!WRXIlX ?Nuu7};wuJ~a 3~{/m1@4{*<؈lJOev<$霒}lu6 Ndm] ;u$2CNY8- IF3/-δ]>]?nL0Lf,*r!L&BY2n{EdBҹb_<у!Oiul K.ո'>]MMr?N">g?Otcm$IRҜy -[8g2I[06nVobwH^""/R&\ۡ .;Il)/> V#4?%*5dA,6Ӂwu *E+o^G7<Wxg$LE39@rDz`L_-{ɚ?r՛p].TSO9Urϧ/ t{%\([ڍ@1زe ӳبt[8w' IDAT,ΔJ6 Q驛.D#&88E7$3᤭|=nKnaOh~"VwqWۊ):Nm ;qla_|(IK.y{hW)Oʞdcո.2o|>+_]oZx;ʛ_2qKR5ؽX3~Sb=I?/e lݓR~+&E\'#_WY.l畧uнK g=+F?`"ӡct}zѦO;P1?HQ}rԬEt/jW+vaaE6ypS!;&5_l?Z8ܽO'L1Z)Z7}ܡ4 [6iǼ|cCȷ>ޝ'`UZ6'P\' _ʜwը79L;GM] *VHw!86ٱQ鰟 PJ iub%0ۗl\v^:+}x{O/&m(@nwK(t(/~2̀ӫ8d[z7)I2M{&/fҔ.a"aǎ BkSl _;S&$@/xGO:M+F{{ ,{IPX7$4xd!Xnj̑/ٸ<(GCf*ͅKX0)k%Iܸ>=BPݵ<<"ך~>Iy9u s Pfsn{]&ڝJqۮ<%?WxFm[LwTP! ۂxn-#3h7n?-RGD 3x$[Ö((b \RS6= qMϣC,uyˀ̘qWȤ]yH ';֋OӼ >֍k F3@SGN]i#Cpkl_:p uム3XzLKR'gOJٜ̒ω`Ӧd?!o׆4g[~f#P근*?exEQ-Lc%WW˂99ub4*1AVH*%?-6>c&@D},(gm\v0nC)\X܁hǃ3n 7g/fߘE?~C7>: p D pq~N 3?6/#,!>H(+fʕl9H Cn*V* '^Oe)p`ʦCh ꥜[L 1-7Ͱϳkޔ頒4b^] dN޽y3fl۹Ĥd ـ+X >ѓSl7:kcHNJ,H;vTIJfy6dT<=;}R$eԓ;;wlb]\"Aɒِ,Y> (J2#T "*)V, ijݤXDS(]ǔ_ChD֯g=u/O<@<;w`PDU r!YȚ=qiWkS\'@.}xh'=cҒ KX3o[ޅR78{,7(cf>[.כʦVn3%(ZΞ'1u,~~z/ufI;3]z չ:<#3[(!޵;JRi,Y3ԏd]>)I2Mᄇ;}#*bE<\Vr+) 6f~2Ըq7ڝr|)׈1ųfM<\0HҚ5(\в`ض}B~ "0;n[$g3?6*=.l؈.a̯&n-Mm6@jQ5$9ه)boj4kާ$*873gZ*{Sq).. b.~>?BGHߗU\0T9~gy蓒$tJgΐ",_qlԬS`H#asP=2!$1);O<.N|/e< {ubr8&>|8SgHJֈ!_M.BˀS/cp)qبtp wƓ{jVۘ9~JNn$7'/(MHtT}8;$+0{sв乀C|@2<$j+ILnt*Ú`D6ohdʄ)2a{Fb8S+(4,$R9N$ISk$S=sS|E%?h_, 3}}&>أZ=vg|kna9zY2Y|40sĮ9D{~^1? A>X43AȑDV?vB:>"~Y!DpqJE̵dk}Ÿu@&%E4:SBLcNqhTvL}:!vvv<ס%ޙR75ؽv_̵F0ї. sG\s/7o`YIxÿ_1+nw% s~@( W⚛ v~>77.-. t をQ@/[4nHǃ=u5!۱sA j%u~euTzr XB-y ,~(5gfD֥ә$mnl7Ɵ>dόiI5&S e[ͫtUBFQL s%cZ~m-I@ajxo'BR"O:86ѱQٚWA{⛙pۏQ4$G֯ܔBrmf3ض~%+%L,e/۫sgFe=3~{*7 { XiE:~Ff8{=CDJT'}"fpuB,Y gGnMVDecۯ38mojן',,Ń|oԑԪE̜6U}270TA T#ӟZ&ǔPP(X{6#*Cm%$I:mLfy#ז? g&4;;\:| 7?ެ}I^n'JѠ-S!ozo"=_1j.K|Q\6 m*Lw ;vqnwRត_2q_+7䑏o]Dw|_[C#x祷8z.֝s]ь^.L#Pjt}B9|$%CT>nڌ $t jkJϞ=YdIBqϥnIݶc⿠s̓Lw?7gm;=ʴO0泎Dfv< ZrXtif!WƆ B3;e>Gf\\ϼ,B}%INYի=zHWbȐ!:OZ(ucWn*Ϣ3wġ$I:% 'mG>C[@s4zA$I2Y˖澮^љ ?amkS#:[%I2Y^\{]RRQܷMZw[J$sҗ<7{)W4L(Вf($I-ץm0Yر7 KpiZWtSIsQ|ђ2; I:86\g>e/ߊ}[}%I P$I$IߏCI$I$I<$I$ICI$I$I<$I$ICI$I$Ie!Cdv$I$IfP:C.]J3; I$I sٲ$I$ILJ$I$I $I$IP ҄ 2;IJUÆ 3;I$I& ҥKf IőaH$Ipٲ$I$ILJ$I$I $I$ILJ$I$I (p-g23cT._Kmm]Qğzk[ YC)p?dv($I$IH_pl^;JupHgWslٗev4$I$I}lu6t g:]*օzV3`ev8$I$Iv0y1>9BHh('?d̎F$I$I:#L6+!oV4y/\<a~&ӝ!JUj^~=5 ݳb<؞+jGSJ Mndz;䕮hQ:U*VWr/mN%i<ѱ9u+Sz]Zt~Ofn>IcjnWZʗw肓k[5!$ѾvkSB $[|)[?$I$ܕfp l&Z5vRk;xm"Ҫ%Ⱥb_}g}:]y_J, RiS\!Ͷ4jքp<~}Kn\Gqe:>ē')-]wc\\ l=֞kMY8~Ӻ [{>MًQqk"b1iP=fv$I$I5ë14U;y|֒ße\*䆱mFwo@P y+jCwC[6iǼ|cYNxȷ>ޝ1P-#xXyYԡ łwcN8ɾ^LPΗPОI'ӻ ;_7oU)`*z~wRUEhp:^ќ 4<ڂ{NOZ$I$Igg&/fҔ.ZpxH w.0$װ>8)Hݻo2} گ?fl<ոg$qAyQ.3,qV+cl6)'J$I$Ry}3<ɟSCSrRd`5Eԧ܉JR2;۰@E0kq99 uueS;Y. vŽw%_T_]iSGOU(Ug%UXL>$I$I-' i> )R$ijfM<\LҚ5(\вu6f~2ȸqU42+h cMb@ O&Po$I$Ii",_g1 85b8Nb#*ɿNz5 :eA?d9 \D m}lذO'&a!L$I$Iҹ#ʼn ~gx({kӑ0vƛ<52^ ^gNCֹ1c_穆SPyʡ{*I$Ig2dHꥹJㆄU/Eʼ&̝Sb~CIDAT}a:7+t;^*~Wc1Xf'Q 0E4;U'w3v:P-?ߏfȔ_v ^P݇ZofG(I$I殾 jkJϞ=YdIBqϥnI6n;۾z2#T&LK.$GBBBf!I$,P|yzEtt4ʕKe=z $$SL9$ƮTbyEA?X >xIJLJ$I$鬗V9]C¢ks#6_ʶ6`9wg|HI$I$ƞXjSIs7{)W4h[[fv$I$IRPʕ+ҥK;U7P$I$I9-fCI$I$o#އ'u&%I$Icg5PQqqq$I$IRaj aH$I$eHll,gNWY-K$I$ICi:3˗/F$I$IZ %OOD$I$IyG۷?Q$I$I$$I$ILJ$I$I $I$ILJ$I$I $I$ILJ$I$I (3dȐ̌C$I$IY&h߾}f!I$I$,P\9z lY$I$II<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$I<$I$ICI$I$Ie=ʕˬ8$I$I$e$۷oҥK33I$I$Ig-K$I$I $I$IBڙ$I$IO<$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$IXIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/docs/index.rst0000644000175000017500000012312614203121554014156 0ustar00olesoles****************** The PyRAF Tutorial ****************** :Authors: - Richard L. White - Perry Greenfield - Ole Streicher :Abstract: PyRAF, based on the Python scripting language, is a command language for IRAF that can be used in place of the existing IRAF CL. This document provides a tutorial introduction to the PyRAF system, showing how it can be used to run IRAF tasks. It describes how to run IRAF tasks using either the familiar IRAF syntax or Python syntax. It also describes PyRAF’s graphical parameter editor and the graphics system. Introduction ============ Why a New IRAF CL? ------------------ The IRAF_ CL has some shortcomings as a scripting language that make it difficult to use in writing scripts, particularly complex scripts. The major problem is that the IRAF CL has no error or exception handling. If an error occurs, the script halts immediately with an error message that has little or no information about where the error occurred. This makes it difficult to debug scripts, impossible to program around errors that can be anticipated, and difficult to provide useful error messages to the user. But there are other important reasons for wanting to replace the CL. We want to use a command language that is a stronger environment for general programming and that provides more tools than the existing IRAF CL. Python_ is a free, popular scripting language that is useful for a very wide range of applications (e.g., scientific analysis, text processing, file manipulation, graphical programming, etc.). Python is also an open system that can be easily extended by writing new modules in Python, C, or Fortran. By developing a Python interface to IRAF tasks, we open up the IRAF system and make it possible to write scripts that combine IRAF tasks with the numerous other capabilities of Python. Why Should I Use PyRAF? ----------------------- PyRAF can run the vast majority of IRAF tasks without any problems. You can display graphics and use interactive graphics and image display tasks as well. You can even run almost all IRAF CL scripts! While many things work the same way as they do in the IRAF CL, there are some differences in how things are done, usually for the better. The most important difference between PyRAF and the old IRAF CL is that PyRAF allows you to write scripts using a much more powerful language that has capabilities far beyond simply running IRAF tasks. Some of the things that now become possible are: * Easier script debugging. * Ability to trap errors and exceptions. * Access to all the features of Python including: - built-in list (array) and dictionary (hash table) data structures - extensive string handling libraries - GUI libraries that make it straightforward to wrap IRAF tasks with GUI interfaces - web and networking libraries making it easy to generate html, send email, handle cgi requests, etc. * Simple access to other software packages written in C or Fortran. * Ability to read and manipulate FITS files and tables using astropy and numpy. As far as the interactive environment goes, the new system has most of the current features and conveniences of the IRAF CL as well as a number of new features. It is possible to use exactly the same familiar IRAF CL syntax to run IRAF tasks (e.g., ``imhead dev$pix long+``). There are some additional features such as command-history recall and editing using the arrow keys, tab-completion of commands and filenames, a GUI equivalent to the epar parameter editor, the ability to display help in a separate window, and a variety of tools for manipulation of graphics. We believe the new environment is in many ways more convenient for interactive use than the IRAF CL. Prerequisites ------------- This guide will not attempt to serve as a Python introduction, tutorial or guide. There are a number of other documents that serve that role well. At some point we recommend spending a few hours familiarizing yourself with Python basics. Which Python documentation or books to start with depends on your programming background and preferences. Some people will find quick introductions like the `Python tutorial`_ all they need. In any case, do not spend too much time reading before actually starting to run PyRAF and use Python. The Python environment is an interactive one and one can learn quite a bit by just trying different things after a short bit of reading. In fact, you may prefer to try many of the examples given in this guide before reading even a single Python document. We will give enough information for you to get started in trying the examples. Our approach will be largely based on examples. This guide also is not intended to be an introduction to IRAF. We assume that current PyRAF users are relatively experienced with IRAF. Check the `Beginner’s Guide to Using IRAF`_ for an (outdated) introduction. Installation ============ To install PyRAF, it is required to have both IRAF_ and Python_ already installed. For the IRAF installation, it is recommended to use a version from iraf-community_, as these include many bugs fixed for current Linux and macOS versions and architectures. Both a self-compiled and a binary IRAF package (f.e. in Ubuntu_) will work. The IRAF installation should have a properly configured environment, especially the ``iraf`` environment variable must be set to point to the IRAF installation directory (i.e. to ``/usr/lib/iraf/`` on Ubuntu or Debian systems). On multi-arch IRAF installations, the ``IRAFARCH`` environment variable should specify the architecture to use. This is usually already set during the IRAF installation procedure. The minimal Python required for PyRAF is 3.6, but it is recommended to use the latest available version. An installation in an virtual environment like venv_ or conda_ is possible. On some Linux distributions, PyRAF is readily available as a binary package and can be installed with the package installer, like ``sudo apt install python3-pyraf`` on Debian or Ubuntu. On all other systems, the package can be installed via PyPI_ with the command ``pip3 install pyraf``. Note that if no binary installation is available on PyPI, the package requires a compilation, so aside from pip3, the C compiler and development libraries (on Linux ``libx11-dev``) should be installed. Starting and Using PyRAF Interactively ====================================== If PyRAF is installed (and your environment is properly configured), typing ``pyraf`` will start up PyRAF using the front-end interpreter. (There are a few command line options, described in :ref:`Command Line Options`, that can be listed using ``pyraf --help``.) Here is an illustration of starting PyRAF and running some IRAF tasks using the CL emulation syntax. :: $ pyraf NOAO/IRAF community V2.16.1+ snapshot Mon, 24 Aug 2021 10:27:34 +0000 This product includes results achieved by the IRAF64 project in 2006- 2009 directed by Chisato Yamauchi (C-SODA/ISAS/JAXA). Welcome to IRAF. To list the available commands, type ? or ??. To get detailed information about a command, type `help '. To run a command or load a package, type its name. Type `bye' to exit a package, or `logout' to get out of the CL. Visit http://github.com/iraf-community/iraf/issues to report problems. The following commands or packages are currently defined: … --> imheader dev$pix long+ dev$pix[512,512][short]: m51 B 600s No bad pixels, min=-1., max=19936. Line storage mode, physdim [512,512], length of user area 1621 s.u. Created Mon 23:54:13 31-Mar-1997, Last modified Mon 23:54:14 31-Mar-1997 Pixel file "HDR$pix.pix" [ok] ’KPNO-IRAF’ / ’31-03-97’ / IRAF-MAX= 1.993600E4 / DATA MAX IRAF-MIN= -1.000000E0 / DATA MIN IRAF-BPX= 16 / DATA BITS/PIXEL IRAFTYPE= ’SHORT’ / PIXEL TYPE CCDPICNO= 53 / ORIGINAL CCD PICTURE NUMBER … HISTORY ’24-04-87’ HISTORY ’KPNO-IRAF’ / HISTORY ’08-04-92’ / --> imstat dev$pix # IMAGE NPIX MEAN STDDEV MIN MAX dev$pix 262144 108.3 131.3 -1. 19936. --> imcopy dev$pix mycopy.fits dev$pix -> mycopy.fits You may notice a great similarity between the PyRAF login banner and the IRAF login banner. That’s because PyRAF reads your normal :file:`login.cl` file and goes through exactly the same startup steps as IRAF when a session begins. If you have customized your :file:`login.cl` or :file:`loginuser.cl` files to load certain packages, define tasks, etc., then those customizations will also take effect in your PyRAF environment. You can start up PyRAF from any directory; unlike the IRAF CL, you are not required to change to your IRAF home directory. PyRAF determines the location of your IRAF home directory by looking for your :file:`login.cl` file, first in your current working directory and then in a directory named :file:`iraf` in your home directory. So as long as your IRAF home directory is :file:`~/iraf` or :file:`~/.iraf`, you can start up PyRAF from any working directory. (You can start from other directories as well, but without access to :file:`login.cl` your IRAF environment will be only partly initialized. We expect to add a startup configuration file, :file:`.pyrafrc`, that allows you customize your initial PyRAF configuration including your IRAF home directory.) The first time you run PyRAF, it creates a :file:`pyraf` directory in your IRAF home directory. All it stores there is a file named :file:`clcache.sqlite3`, which is used to save translated versions of your own custom CL scripts. Note that the task syntax shown above is identical to that of the IRAF CL. But there is no escaping that you are really running in a Python environment. Should you make a mistake typing a task name, for example, :: --> imstart dev$pix File "", line 1 imstart dev$pix ^ SyntaxError: invalid syntax or should you use other CL-style commands, :: --> =cl.menus File "", line 1 =cl.menus ^ SyntaxError: invalid syntax then you’ll see a Python error message. At this stage, this is the most likely error you will see aside from IRAF-related ones. Aside from some noticeable delays (on startup, loading graphics modules, or in translating CL scripts not previously encountered), there should be little difference between running IRAF tasks in CL emulation mode and running them in the IRAF CL itself. New Capabilities in PyRAF ------------------------- Several capabilities in the PyRAF interpreter make it very convenient for interactive use. The up-arrow key can be used to recall previous commands (no need to type ``ehis``!), and once recalled the left and right arrow keys can be used to edit it. The :kbd:`Ctrl`-:kbd:`R` key does pattern-matching on the history. Just type part of the command (not necessarily at the beginning of the line) and you’ll see the matched command echoed on the command line. Type :kbd:`Ctrl`-:kbd:`R` again to see other matches. Hit return to re-execute a command, or other line-editing keys (left/right arrow, :kbd:`Ctrl`-:kbd:`E`, :kbd:`Ctrl`-:kbd:`A`, etc.) to edit the recalled command. There are many other ways to search and manipulate the history – see the gnu readline documentation for more information. The tab key can be used to complete commands, in a way familiar to users of tcsh and similar shells. At the start of the command line, type ``imhe``:kbd:`Tab` and PyRAF fills in ``imheader``. Then type part of a filename :kbd:`Tab` and PyRAF fills in the rest of the name (or fills in the unambiguous parts and prints a list of alternatives). This can be a great timesaver for those long HST filenames! You can also use tab to complete IRAF task keyword names (e.g., ``imheader lon``:kbd:`Tab` fills in ``longheader``, to which you can add ``=yes`` or something similar). And when using Python syntax (see below), tab can be used to complete Python variable names, object attributes, etc. The function :: saveToFile filename saves the current state of your PyRAF session to a file (including package, task, and IRAF environment variable definitions and the current values of all task parameters). The function :: restoreFromFile filename restores the state of your session from its previously saved state. A save filename can also be given as a Unix command line argument when starting up PyRAF, in which case PyRAF is initialized to the state given in that file. This can be a very useful way both to start up in just the state you want and to reduce the startup time. Differences from the CL and Unimplemented CL Features ----------------------------------------------------- Some differences in behavior between PyRAF and the CL are worth noting. PyRAF uses its own interactive graphics kernel when the CL ``stdgraph`` variable is set to a device handled by the CL itself (e.g., xgterm). If ``stdgraph`` is set to other values (e.g. ``stdplot`` or the imd devices), the appropriate CL task is called to create non-interactive plots. Only the default PyRAF graphics window supports interactive graphics (so you can’t do interactive graphics on image display plots, for example.) Graphics output redirection is not implemented. Some IRAF CL commands have the same names as Python commands; when you use them in PyRAF, you get the Python version. The ones most likely to be encountered by users are :func:`print` and :keyword:`del`. If you want to use the IRAF print command (which should rarely be needed), use ``clPrint`` instead. If you want the IRAF ``delete`` command, just type more of the command (either ``dele`` or ``delete`` will work). Another similar conflict is that when an IRAF task name is identical to a reserved keyword in Python (to see a list, do ``import keyword; print(keyword.kwlist)``), then it is necessary to prepend a ``PY`` (yes, in capital letters) to the IRAF task name. Such conflicts should be relatively rare, but note that :keyword:`lambda` and :keyword:`in` are both Python keywords. The PyRAF help command is a little different than the IRAF version. If given a string argument, it looks up the CL help and uses it if available. For other Python argument types, ``help`` gives information on the variable. E.g., ``help(module)`` gives information on the contents of a module. There are some optional arguments that are useful in Python programs (type ``help(help)`` for more information). If you need to access the standard IRAF help command without the additional PyRAF features, use ``system.help taskname options``. Note that the IRAF help pages are taken directly from IRAF and do not reflect the special characteristics of PyRAF. For example, if you say ``help while``, you get help on the CL while loop rather than the Python while statement. The login message on startup also comes directly from IRAF and may mention features not available (or superseded) in PyRAF. There are a few features of the CL environment and CL scripts that are not yet implemented: Packages cannot be unloaded. There is no way to unload a loaded IRAF package. The bye command exists but does not do anything; the keep command also does nothing (effectively all modifications to loaded tasks and IRAF environment variables are kept). No GOTO statements in CL scripts. Python does not have a :keyword:`goto` statement, so converting CL scripts that use goto’s to Python is difficult. We have made no effort to do such a conversion, so CL scripts with GOTO’s raise exceptions. GOTOs may not ever get implemented. Background execution is not available. Background execution in CL scripts is ignored. Error tracebacks in CL scripts do not print CL line numbers. When errors occur in CL scripts, the error message and traceback refer to the line number in the Python translation of the CL code, not to the original CL code. (If you want to see the Python equivalent to a CL task, use the getCode method – e.g. ``print(iraf.spy.getCode())`` to see the code for the ``spy`` task). The EPAR Parameter Editor ========================= For PyRAF we have written a task parameter editor that is similar to the IRAF epar function but that uses a graphical user interface (GUI) rather than a terminal-based interface. PyRAF’s EPAR has some features not available in the IRAF CL, including a file browser for selecting filename parameters and a pop-up window with help on the task. Upon being invoked in the usual manner in IRAF CL emulation mode, an EPAR window for the named task appears on the screen: :: --> epar ccdlist An EPAR window consists of a menu bar, current package and task information, action buttons, the parameter editing panel, and a status box. If there are more parameters than can fit in the displayed window, they will appear in a scrolling region. .. image:: epar.png Action Buttons -------------- The EPAR action buttons are: Execute Execute the task with parameter values currently displayed in the EPAR windows. (Several windows may be open at once if the task has PSET parameters – see below.) If parameter values were changed and not saved (via the Save button), these new values are automatically verified and saved before the execution of the task. The EPAR window (and any child windows) is closed, and the EPAR session ends. Save This button saves the parameter values associated with the current EPAR window. If the window is a child, the child EPAR window closes. If the window is the parent, the window closes and the EPAR session ends. The task is not executed. Unlearn This button resets all parameters in the current EPAR window to their system default values. Cancel This button exits the current EPAR session without saving any modified parameter values. Parameters revert to the values they had before EPAR was started; the exception is that PSET changes are retained if PSETs were editted and explicitly saved. Task Help This button displays the IRAF help information for a task. Menu Bar -------- The EPAR menu bar consists of **File**, **Options**, and **Help** menus. All of the **File** menu choices map directly to the action button functionality. The **Options** menu allows the user to choose the way help pages are displayed; the information can be directed to the user’s web browser or to a pop-up window (the default). The **Help** menu gives access to both the IRAF task help and information on the operation of EPAR itself. Parameter Editing Panel ----------------------- Different means are used to set different parameter types. Numeric and string parameters use ordinary entry boxes. Parameters with an enumerated list of allowed values use choice lists. Booleans are selected using radio buttons. PSETs are represented by a button that when clicked brings up a new EPAR window. PSET windows and the parent parameter windows can be edited concurrently (you do not have to close the child window to make further changes in the parent window). Parameters may be editted using the usual mouse operations (select choices from pop-up menus, click to type in entry boxes, and so on.) It is also possible to edit parameter without the mouse at all, using only the keyboard. When the editor starts, the first parameter is selected. To select another parameter, use the :kbd:`Tab` or :kbd:`Return` key (:kbd:`Shift`-:kbd:`Tab` or :kbd:`Shift`-:kbd:`Return` to go backwards) to move the focus from item to item. The :kbd:`↑` and :kbd:`↓` arrow keys also move between fields. Use the :kbd:`space` bar to “push” buttons, activate pop-up menus, and toggle boolean values. The toolbar buttons are also accessible from the keyboard using the :kbd:`Tab` and :kbd:`Shift`-:kbd:`Tab` keys. They are located in sequence before the first parameter and after the last parameter (since the item order wraps around at the end.) 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. See the **Help→Epar Help** menu item for more information on keyboard shortcuts. Parameters entered using entry boxes (strings and numbers) are checked for correctness when the focus shifts to another parameter (either via the :kbd:`Tab` key or the mouse.) The parameter values are also checked when either the Save or Execute button is clicked. Any resulting errors are either displayed in the status area at the bottom (upon validation after return or tab) or in a pop-up window (for Save/Execute validation). For parameters other than PSETs, the user can click the right-most mouse button within the entry box or choice list to generate a pop-up menu. The menu includes options to invoke a file browser, clear the entry box, or unlearn the specific parameter value. “Clear” removes the current value in the entry, making the parameter undefined. “Unlearn” restores the system default value for this specific parameter only. The file browser pops up an independent window that allows the user to examine the directory structure and to choose a filename for the entry. 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.) Status Line ----------- Finally, the bottom portion of the EPAR GUI is a status line that displays help information for the action buttons and error messages generated when the parameter values are checked for validity. PyRAF Graphics and Image Display ================================ PyRAF has its own built-in graphics kernel to handle interactive IRAF graphics. Graphics tasks can be run from any terminal window — there is no need to use the IRAF xgterm. If the value for stdgraph set in your login.cl would have the IRAF CL use its built-in graphics kernel, in PyRAF it will use PyRAF’s built-in kernel. The PyRAF kernel is not identical to IRAF’s but offers much the same functionality — it lacks some features but adds others. If you specify a device that uses other IRAF graphics kernels (e.g., for printers or image display plots), PyRAF will use the IRAF graphics kernel to render those plots. There are some limitations when using IRAF kernels. For example, it is not possible to use interactive graphics tasks with those kernels. But otherwise, most of their functionality is available. The PyRAF built-in graphics kernel is based on OpenGL and Tkinter. Graphics windows are created from PyRAF directly. One can run a graphics task like any other. For example, typing :: --> prow dev$pix 256 will create a graphics window and render a plot in it. The graphics window is responsive at all times, not just while an IRAF task is in interactive graphics mode. If the window is resized, the plot is redrawn to fit the new window. There is a menu bar with commands allowing previous plots to be recalled, printed, saved, etc. The **Edit→Undo** menu command can remove certain graphics elements (e.g., text annotations and cursor marks.) It is possible to create multiple graphics windows and switch between them using the **Window** menu. See the **Help** menu for more information on the capabilities of the PyRAF graphics window. Some options (such as the default colors) are easily configurable. .. image:: prow.png Interactive graphics capability is also available. For example, typing ``implot dev$pix`` will put the user into interactive graphics mode. The usual graphics keystroke (gcur) commands recognized by the task will work (e.g., lowercase letter commands such as c) and colon commands will work as they do in IRAF. Most CL-level (capital letter) keystroke commands have not yet been implemented; the following CL level commands are available: * The arrow keys move the interactive cursor one pixel. :kbd:`Shift` combined with the arrow keys moves the cursor 5 pixels. * **C** prints the current cursor position on the status line. * **I** immediately interrupts the task (this is the gcur equivalent to control-C). * **R** redraws the plot with annotations removed (also available through the **Edit→Undo All** menu item.) * **T** annotates the plot at the current cursor position, using a dialog box to enter the text. * **U** undoes the last “edit” to a plot (annotations or cursor markers). This can be repeated until only the original plot remains. (Also available using **Edit→Undo**.) * A colon (**:**) prompts on the status line for the rest of the colon command. Other input from interactive graphics tasks may also be done from the status line. * The **:.markcur** directive is recognized. It toggles the cursor marking mode (**:.markcur+** enables it, **:.markcur-** disables it). This directive cannot be abbreviated. Help for interactive IRAF tasks can usually be invoked by typing **?**; the output appears in the terminal window. Output produced while in cursor-mode (e.g., readouts of the cursor position) appear on the status line at the bottom of the graphics window. Note that the status line has scrollbars allowing previous output to be recalled. PyRAF attempts to manipulate the window focus and the cursor location in a sensible way. For example, if you start an interactive graphics task, the mouse position and focus are automatically transferred to the graphics window. If the task does not appear to be responding to your keyboard input check to see that the window focus is on the window expecting input. Printing Graphics Hardcopy -------------------------- It is possible to generate hard copy of the plotted display by using the **File→Print** menu item or, in gcur mode, the equal-sign (=) key. PyRAF will use the current value of stdplot as the device to plot to for hardcopy. Inside scripts, a hardcopy can be printed by :: --> from pyraf.gki import printPlot # only need this once per session --> printPlot() This could be used in a Python script that generates graphics using IRAF tasks. It is also possible to do other graphics manipulations in a script, e.g., changing the display page. Multiple Graphics Windows ------------------------- It is possible to display several graphics windows simultaneously. The **Window→New** menu item can create windows, and the **Window** menu can also be used to select an existing window to be the active graphics window. Windows can be destroyed using the **File→Quit Window** menu item or directly using the facilities of the desktop window manager (close boxes, frame menus, etc.) It is also possible to create new windows from inside scripts. If you type: :: --> from pyraf import gwm # only need this once per session --> gwm.window("My Special Graphic") you will create a new graphics window which becomes the current plotting window for PyRAF graphics. The :func:`gwm.window()` function makes the named window the active graphics window. If a graphics window with that name does not yet exist, a new one is created. Windows can be deleted by closing them directly or using :func:`gwm.delete()`. Using these commands, one can write a script to display several plots simultaneously on your workstation. Other Graphics Devices ---------------------- To plot to standard IRAF graphics devices such as xterm or xgterm one can :: --> set stdgraph = stgkern --> iraf.stdgraph.device = "xgterm" or whatever device you wish to use. [Note the Python version of the set statement is ``iraf.set(stdgraph="stgkern")``]. In this way it is possible to generate plots from a remote graphics terminal without an Xwindows display. The drawback is that is is not possible to run interactive graphics tasks (e.g., ``implot`` or ``splot``) using this approach. It may be necessary to call :func:`iraf.gflush()` to get the plot to appear. One can generate plots to other devices simply by setting ``stdgraph`` to the appropriate device name (e.g., ``imdr`` or ``stdplot``). Only special IRAF-handled devices such as ``xgterm`` and ``xterm`` need to use the “magic” value ``stgkern`` for ``stdgraph``. IRAF tasks such as ``tv.display`` that use the standard image display servers (**ximtool**, **SAOImageDS9**) should work fine. Interactive image display tasks such as ``imexamine`` work as well (as long as you are using the PyRAF graphics window for plotting). Graphics output to the image display (allowing plots to overlay the image) is supported only through the IRAF kernel, but a PyRAF built-in kernel is under development. Running Tasks in Python Mode ============================ If that’s all there was to it, using PyRAF would be very simple. But we would be missing much of the point of using it, because from CL emulation mode we can’t access many of the powerful programming features of the Python language. CL emulation mode may be comfortable for IRAF users, but when the time comes to write new scripts you should learn how to run IRAF tasks using native Python syntax. There are a number of ways of running tasks and setting parameters. The system is still under development, and depending on user feedback, we may decide to eliminate some of them. Below we identify our preferred methods, which we do not intend to eliminate and which we recommend for writing scripts. The PyRAF Interpreter Environment --------------------------------- When the PyRAF system is started using the **pyraf** command as described previously, the user’s commands are actually being passed to an enhanced interpreter environment that allows use of IRAF CL emulation and provides other capabilities beyond those provided by the standard Python interpreter. In fact, when **pyraf** is typed, a special interpreter is run which is a front end to the Python interpreter. This front-end interpreter handles the translation of CL syntax to Python, command logging, filename completion, shell escapes and the like which are not available in the default Python interpreter. It is also possible to use PyRAF from a standard Python session, which is typically started by simply typing **python3** at the Unix shell prompt. In that case the simple CL syntax for calling tasks is not available and tab-completion, logging, etc., are not active. For interactive use, the conveniences that come with the PyRAF interpreter are valuable and we expect that most users will use PyRAF in this mode. One important thing to understand is that the alternate syntax supported by the PyRAF front end interpreter is provided purely for interactive convenience. When such input is logged, it is logged in its translated, Python form. Scripts should always use the normal Python form of the syntax. The advantage of this requirement is that such scripts need no preprocessing to be executed by Python, and so they can be freely mixed with any other Python programs. In summary, if one runs PyRAF in its default mode, the short-cut syntax can be used; but when PyRAF is being used from scripts or from the standard Python interpreter, one must use standard Python syntax (not CL-like syntax) to run IRAF tasks. Even in Python mode, task and parameter names can be abbreviated and, for the most part, the minimum matching used by IRAF still applies. As described above, when an IRAF task name is identical to a reserved keyword in Python, it is necessary to prepend a ``PY`` to the IRAF task name (i.e., use ``iraf.PYlambda``, not ``iraf.lambda``). In Python mode, when task parameters conflict with keywords, they must be similarly modified. The statement ``iraf.imcalc(in="filename")`` will generate a syntax error and must be changed either to ``iraf.imcalc(PYin="filename")`` or to ``iraf.imcalc(input="filename")``. This keyword/parameter conflict is handled automatically in CL emulation mode. Some of the differences between the PyRAF interpreter and the regular Python interpreter besides the availability of CL emulation mode: ====================== ============================ ============================= command PyRAF interpreter Python default interpreter ====================== ============================ ============================= prompt ``-->`` ``>>>`` print interpreter help ``.help`` n/a exit interpreter ``.exit`` EOF (:kbd:`Ctrl`-:kbd:`D`) or ``sys.exit()`` start logging input ``.logfile`` filename n/a append to log file ``.logfile`` filename append n/a stop logging input ``.logfile`` n/a run system command ``!`` command ``os.system(’command’)`` start a subshell ``!!`` ``os.system(’/bin/sh’)`` ====================== ============================ ============================= Example with Standard Python Syntax ----------------------------------- This example mirrors the sequence for the example given above in the discussion of CL emulation (:ref:`Starting and Using PyRAF Interactively`). In the discussion that follows we explain and illustrate some variants. :: $ pyraf … --> iraf.imheader("dev$pix", long=yes) dev$pix[512,512][short]: m51 B 600s No bad pixels, min=-1., max=19936. Line storage mode, physdim [512,512], length of user area 1621 s.u. Created Mon 23:54:13 31-Mar-1997, Last modified Mon 23:54:14 31-Mar-1997 Pixel file "HDR$pix.pix" [ok] ’KPNO-IRAF’ / ’31-03-97’ / IRAF-MAX= 1.993600E4 / DATA MAX IRAF-MIN= -1.000000E0 / DATA MIN IRAF-BPX= 16 / DATA BITS/PIXEL IRAFTYPE= ’SHORT’ / PIXEL TYPE CCDPICNO= 53 / ORIGINAL CCD PICTURE NUMBER … HISTORY ’24-04-87’ HISTORY ’KPNO-IRAF’ / HISTORY ’08-04-92’ / --> iraf.imstat("dev$pix") # IMAGE NPIX MEAN STDDEV MIN MAX dev$pix 262144 108.3 131.3 -1. 19936. --> imcopy("dev$pix", "mycopy.fits") dev$pix -> mycopy.fits The mapping of IRAF CL syntax to Python syntax is generally quite straightforward. The most notable requirements are: * The task or package name must be prefixed with ``iraf.`` because they come from the iraf module. In scripts, use :: from pyraf import iraf to load the iraf module. Note that the first time PyRAF is imported, the normal IRAF startup process is executed. It is also possible to import tasks and packages directly using :: from pyraf.iraf import noao, onedspec With this approach, packages are automatically loaded if necessary and tasks can be used without the iraf. prefix. Like the IRAF CL, packages must be loaded for tasks to be accessible. * The task must be invoked as a function in Python syntax (i.e., parentheses are needed around the argument list). Note that parentheses are required even if the task has no arguments – e.g., use ``iraf.fitsio()``, not just ``iraf.fitsio``. * String arguments such as filenames must be quoted. Another change is that boolean keywords cannot be set using appended ``+`` or ``-`` symbols. Instead, it is necessary to use the more verbose ``keyword=value`` form (e.g., ``long=yes`` in the example above). We have defined Python variables ``yes`` and ``no`` for convenience, but you can also simply say ``long=True`` to set the (abbreviated) ``longheader`` keyword to true. Emulating pipes in Python mode is also relatively simple. If a parameter ``Stdout=True`` is passed to a task, the task output is captured and returned as a list of Python strings, with one string for each line of output. This list can then be processed using Pythons sophisticated string utilities, or it can be passed to another task using the Stdin parameter: :: --> s = iraf.imhead("dev$pix", long=yes, Stdout=1) --> print(s[0]) dev$pix[512,512][short]: m51 B 600s --> iraf.head(nl=3, Stdin=s) dev$pix[512,512][short]: m51 B 600s No bad pixels, min=-1., max=19936. Line storage mode, physdim [512,512], length of user area 1621 s.u. ``Stdin`` and ``Stdout`` can also be set to a filename or a Python filehandle object to redirect output to or from a file. ``Stderr`` is also available for redirection. Note the capital ``S`` in these names – it is used to eliminate possible conflicts with task parameter names. Setting IRAF Task or Package Parameters --------------------------------------- There are multiple ways of setting parameters. The most familiar is simply to provide parameters as positional arguments to the task function. For example :: --> iraf.imcopy("dev$pix", "mycopy.fits") Alternatively, one can set the same parameters using keyword syntax: :: --> iraf.imcopy(input="dev$pix", output="mycopy.fits") Hidden parameters can only be set in the argument list this way (analogous to IRAF). As in the IRAF CL, the parameter values are learned for non-hidden parameters (depending on the mode parameter settings) but are not learned (i.e., are not persistent) for hidden parameters. But parameters can also be set by setting task attributes. For example: :: --> iraf.imcopy.input = "dev$pix" --> iraf.imcopy.output = "mycopy.fits" --> iraf.imcopy() # run the task with the new values These attribute names can be abbreviated (don’t expect this behavior for most Python objects, it is special for IRAF task objects): :: --> iraf.imcopy.inp = "dev$pix" --> iraf.imcopy.o = "mycopy.fits" PyRAF is flexible about the types used to specify the parameter so long as the conversion is sensible. For example, one can specify a floating point parameter in any of the following ways: :: --> iraf.imstat.binwidth = "33.0" --> iraf.imstat.binwidth = "33" --> iraf.imstat.bin = 33.0 --> iraf.imstat.bin = 33 but if the following is typed: :: --> iraf.imstat.bin = "cow" Traceback (innermost last): File "", line 1, in ? ValueError: Illegal float value ’cow’ for parameter binwidth An error traceback results. When running in the PyRAF interpreter, a simplified version of the traceback is shown that omits functions that are part of the pyraf package. The ``.fulltraceback`` command (which can be abbreviated as can all the executive commands) will print the entire detailed traceback; it will probably only be needed for PyRAF system developers. Python tracebacks can initially appear confusing, but they are very informative once you learn to read them. The entire stack of function calls is shown from top to bottom, with the most recently called function (where the error occurred) listed last. The line numbers and lines of Python code that generated the error are also given. One can list the parameters for a task using one of the following commands (in addition to the usual IRAF ``lpar imcopy``): :: --> iraf.imcopy.lParam() --> iraf.lpar(iraf.imcopy) # Note there are no quotation marks --> iraf.lpar(’imcopy’) For those who have encountered object-oriented programming, ``iraf.imcopy`` is an ’IRAF task object’ that has a method named ``lParam`` that lists task parameters. On the other hand, ``iraf.lpar`` is a function (in the iraf module) that takes either an IRAF task object or a string name of a task as a parameter. It finds the task object and invokes the ``.lParam()`` method. One can start the EPAR utility for the task using a parallel set of commands: :: --> iraf.imcopy.eParam() --> iraf.epar(iraf.imcopy) --> iraf.epar(’imcopy’) Tasks appear as attributes of packages, with nested packages also found. For example, if you load the ``noao`` package and the ``onedspec`` subpackage, then the ``identify`` task can be accessed through several different means: ``iraf.identify``, ``iraf.noao.identify``, or ``iraf.onedspec.identfy`` will all work. Ordinarily the simple ``iraf.identify`` is used, but if tasks with the same name appear in different packages, it may be necessary to add a package name to ensure the proper version of the task is found. Other Ways of Running IRAF Tasks -------------------------------- One way of reducing the typing burden (interactively or in scripts, though perhaps it isn’t such a good idea for scripts) is to define an alias for the iraf module after it is loaded. One can simply type: :: --> i = iraf --> i.imcopy("dev$pix", "mycopy.fits") --> i.imstat("mycopy.fits") But don’t use i for a counter variable and then try doing the same! E.g., :: --> i = 1 --> i.imcopy(’dev$pix’,’mycopy.fits’) will give you the following error message AttributeError: :: ’int’ object has no attribute ’imcopy’ since the integer `1` has no imcopy attribute. Command Line Options ==================== There are a few command-line options available for PyRAF: -h List the available options. There are long versions of some options (e.g., ``--help`` instead of ``-h``) which are also described. -c command Command passed in as string (any valid PyRAF command) -e Turn on ECL mode -m Run the PyRAF command line interpreter to provide extra capabilities (default) -i Do not start the special PyRAF interpreter; just run a standard Python interactive session -n No splash screen during startup -s Silent initialization (does not print startup messages) -v Set verbosity (repeated ’v’ increases the level; mainly useful for system debugging) -x No graphics will be attempted/loaded during session -y Run the IPython shell instead of the normal PyRAF command shell (only if IPython is installed) A save filename (see :ref:`New Capabilities in PyRAF`) can be given as a command line argument when starting up PyRAF, in which case PyRAF is initialized to the state given in that file. This allows you to start up in a particular state (preserving the packages, tasks, and variables that have been defined) and also reduces the startup time. .. _Python: https://www.python.org/ .. _Python tutorial: https://docs.python.org/3/tutorial/ .. _venv: https://docs.python.org/3/library/venv.html .. _conda: https://docs.conda.io/ .. _PyPI: https://pypi.org/project/pyraf .. _IRAF: https://iraf-community.github.io .. _iraf-community: https://iraf-community.github.io .. _Beginner’s Guide to Using IRAF: https://iraf-community.github.io/doc/beguide.pdf .. _Ubuntu: https://www.ubuntu.com/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1643704417.0 pyraf-2.2.1/docs/ipython_notes.txt0000644000175000017500000000601414176170141015761 0ustar00olesolesIPython support for PyRAF works by replacing the PyRAF command line interpreter with the IPython shell. This is accomplished using IPython's profile mechanism and input prefilter hook. Initiating a PyRAF session using IPython can be accomplished two ways: 1. Run PyRAF with the --ipython switch % pyraf --ipython This has the advantage that it installs the ipythonrc-pyraf profile in your $HOME/.ipython if needed before running ipython. IPython needs to be installed first or it will not install IPython's own rc files in ~/.ipython. 2. Running IPython using the pyraf profile: After IPython, PyRAF, and $HOME/.ipython/ipythonrc-pyraf are installed, you can just run ipython using the pyraf profile. % ipython -p pyraf PyRAF 1.3dev (2006Mar24) Copyright (c) 2002 AURA Python 2.4.2 (#2, Oct 19 2005, 17:07:05) Type "copyright", "credits" or "license" for more information. IPython 0.7.2.svn -- An enhanced Interactive Python. ? -> Introduction to IPython's features. %magic -> Information about IPython's 'magic' % functions. help -> Python's own help system. object? -> Details about 'object'. ?object also works, ?? prints more. IPython profile: pyraf In [1]: Compared to the standard PyRAF command line shell, running PyRAF under IPython has these differences: a. PyRAF's "." directives are rendered obsolete and are not supported. There are IPython equivalents. b. As a result of all the "magic" in IPython and PyRAF, there is the potential for overlap and conflict between the two. By default, IPython's magic commands are executed ahead of PyRAF commands, so if you type "history" you get the IPython command logging system, not the PyRAF system. This is a little counter-intuitive since PyRAF support is added as an input pre-filter and you might expect it to get precedence by default. IPython magic gets precedence but this behavior can be fine tuned using two IPython "magic" commands: In[2]: set_pyraf_magic on In[3]: set_pyraf_magic off This can be further disambiguated by saying "%" which means use the IPython version of . There is currently no equivalent prefix for declaring "use PyRAF". c. There is an option to choose between default IPython exception handling and PyRAF exception handling. PyRAF exception handling has the benefit that it hides the traceback from PyRAF itself; this keeps a user within the realm of their own application rather than debugging IPython or PyRAF. In[4]: use_pyraf_traceback on If you prefer the ipython exception handling system, you can that instead with the following command: In[5]: use_pyraf_traceback off d. PyRAF's support for IPython includes CL emulation which is on by default. This can be turned on/off with the following commands: In[6]: use_pyraf_cl_emulation on In[7]: use_pyraf_cl_emulation off e. PyRAF's support for IPython includes PyRAF-enhanced readline completion which filters input before IPython. It can be turned on and off using: In[8]: use_pyraf_readline_completer on In[9]: use_pyraf_readline_completer off ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/docs/prow.png0000644000175000017500000005467414203121554014025 0ustar00olesolesPNG  IHDRg׷vsBIT|dtEXtSoftwaregnome-screenshot> IDATxwUս?a 6ĆF X$FSQS%7KƒxM7=hb&vcDXJm8?`20Ι= g)df}J%5NIz%閤Ko$I%Yd~yIաBJIvM{Z If[31~Y]%>}я~Wu؎;c.]:tR5TL9\./_~/M?/~ ?yg9eqZ}j0CI% 'pO~cϽv+r=7u?/x㍏9B)IVUNG:%90I>}~Ȑ!RJ9,[$˖-ʕ+R__U=+J)JN;Sڵ[J)ɏOzoxӇgΜ0:=deET;%98I7v/tֹ~e-\%\.W[V*ҭ{C:f+ZTKOkdxn{`~W;9s >&ջ7d{}v[pA 6 PE d=u}z.-~Kn;ο/_~}[lY 6 PRg׫$K,j;TCY+ugjgaǼ6G޵3Nu|JoO.mf̚$?Trخt^qY;2\=rg7jwm9s /οs| fڋ T]/Ι5_ɜ$+|§rӥɥ~.K*|/a_ Ijժ\KұcnjQy?.iVXXϞ=[YM@k(oup9f̬3gPEwYF_<: }0{k_jU.g>FO?\Ӭ\rkY`[M>|nU~|ǷdΚKRv$SN|s-]~ӵ?'WZ קSN?K}}z{ɻqz^6n( @)'={y9ً;-hSbӓ$?O_jU.%k^dKc 7 l>yGrC]}zJ[%}7ǿ=/4%w^d|C_.5ܚo/{6w';9_05'<7kNfYro 9~ԞN?~^ĔW~WgMWoSS99~6lW3n{#3N>s/sڅ>IՑ^§ru}OM͔g_ȼtνwG椓ݓ33~̜b^ZVzwrrSw/^zuÿ}EoK-LTZlXA`fyY`~R3Y)$:xa4'Lz87uRן寷̆Ln=\au~)'n߼9=Hf=Hjx$>F{4˧/E9ч 9am΃Ʌ9/3cѼ̘##N?:{>ym_Ҝ%yiΌuwФkՕ4Z7Y0Vc%x8Sn+@;V~s.^̔k!9bc3xvB5ku\RgVΌ!*^?W\}:.g#,?7{lLzT`c7.rz:mR)}ɂ`b:Y!*g`4\y}ZS漏TgR煻/6sLrG>NU))Npۓ{e^Pg|'2 Sw䔇 YvQ齺{d}?ʻlܝ+ٻ|dqEM3-.ܿ&=އFdu~S*)ȓSZ? 밥 h[觿lt:S\w;?gpLNv켹2a=΀7u‼r%fAVessuN;ǞwtDmN{jRSԩSwk[jU~fʕ5k]Ƕ>+W%-xy3_og'o^׌_NRJ^Cyr_-3IU4޵[ym _oR\s-3vy~gŋO/O/~5i4jkۓGE?d`C?N%OLKْ7x"_2~~s?O W?Lت癩|!{rM7g,'g'lCv=u9n>o˿g&LWeo2yŹ)kCuUV]3֜ʞwswfʗ]̳uM4krm6y/KbeVn3O&.|%O]ݷf2T9d/|:7|t3{SV,[ݥeSɉj8R浟=K SN:̛;'.E 3ot ҹ_o uݖJsգwdn>bm/>~z;5mS)W~y⩩<˗M9=k]^I6_N|Ǩ{3?K'|>Uٽ̛d\zD#2Oy3lȫW]cE6k򚦪48xMƮIfOzye]_Xqo{F(`[G?7̛;'Ozҹsg?sj@8c4e]>%~\xsYdkvYg]M\Ă)]onrVwkk<73^pYI:4cs =;'fʌR/{P^2y7yoz_N*MN ;߀2]'"vȰ3ܞ`|Z=wnQޓ{(㟙9/KK;d]vM߽1c%|Z}}}ϛ%mŋ\.Nm_sF%&ӂmiUЗo$U??9Yp9$I>;=*|GGޖjӂ=*7T{'3|;`}svhuΗ햡J{j<иM]$)}}y5PZCˇ36n~?%sG6znCsޑobaL lwfr3s=}ymv۩>l?Z~A, On+P9P @3j5g8t*g 5)h23j3Mr@ g $(p@Ve;i4Y3tLk(p@3Ӟ}hT+g;h&MtϠAj6UH8P @3H8P @3p|A4hs/,n8A2˹wv6uMyN2/?[_ȐAr{csp>&GyCsWj 9nР :U s9WN??m-2hС?W9&?g[axL.Ӟ$'AC>W> iKXtNsAٽ{c*l={NI*y>{;kVuIGdIRE3䉧rq|51RmjM>dR`yύKopK}"I hn9m8 _aȾѭW*55'~=υ?:n~rRzҷVT:,ë;ۧvzrH_%ɫ<0Sں%9n띲6 ;5-5lCe {g _m59K$ -./G[jjV'TjtGQ<=>ޟ_s]5h{hyP8Ӽc/N|:ykRsrĨ靟L93{ޜկΉ|9@e&tfܞ$/.1?;,JeڬK=W_H75Cs#2Moχsib-I3co3OȨu^?ɪ-ҚÇg$+CaS[Hҩ:Vzq^0okW3>tP|9n#?:ngq'rS3hРwӷ`{"cuO+玟}.giT24G~<60rO<o?3rqLxmu{Cz$/|n|}959ry;Gg;6Cߖ}d[t!9}Gg$^OEz%KTtJ)_ɯ^%/GIs#_~'wR 8']rSyorUڌڣKM3}oxMv5yI[v̈_ujOTgX6W..usvYs>$C3VtNni 9zi01AΞR9dk9}O\wJGὲ3K)Y.8JIixF1&nhefƎec3rYZh2rNˏ5?-%9*o2?O;=9K^a73V3s}]V?T?|ʥW|oޱؾG9.Y`κ,Om$I=5?K-yМWpAƔ3;t?pwsQGg^>}sݒo^cӵ_53K{^o_V_${x]x陼eUOnNrOKd9tfLjk:'IV_WI_Kj˷~>fߝso~_~z:+4sWo!?:5,397/T~i$2bā=3f̴=VxLƌO>?1cKFu{ߪ׈TbL7\V\.XL?|? -82[5MϯNk|]ڢy.vUv|s^~#o0]qC^lgM?ALÙvr¤W{~r ogѬrG~Pu[j|Lt읣=7으[S4^,Nz`&Ie_{l[{#5SkrYy63zc; ,urR]m'yU%|`&I:~u>ޑIeܘ;iCnt|sm0$[O=Y:.#k0bD%0fL9V_7&u_˫{'O <<&c$m7]2cN9_}9In䬯OJ揹m᦯6P99l;=ws"c=6+R엹<k):qBIrT3ԸouurړNxg9Eh[hxUSg(&%g<2eF-Z$ 3sӳI$Uy͛Kύ1 o>ߣ78^{Ǫó<N: Ok^թ\V[gS3'O'Ju٪&GFwљ;kf&k*9k.ַeǜtgʸA ̈uuS[ZJ&9 #<6u͟o1ߛvO36Tޤ 1`[q̤G`SJw>'-CR;>yˑMjŵpwN-Lz{pR͜b~J mVs2cF}~٣F)-3]٧GRt390GĜ}sai& 7,Z̜YdÈgR6 g |ԤrÌ759|R[<8,kӐ!9V]R7~÷2{}izL$s/435\+{U{I1#3LUuFTW庻f1OϘ%}2`䰔n˘ 2v$2bKrF3S% IDAT?IG’4mA™({^l̾9M*~[IįI!ұ?cusr͠ٻWuJ)ӏ޷)%.Ssx)7~, >:=s&ܟsZϺܷ,5\ԡR?g5#G8$롌t3ѤIGfH#20d̘ @9y )ۭʌJvZ`6NÙ]oɄYӰ|#KmƮ۷*83M[ ->mZ }V`']ySfk[o^=w5ݓ(?czK= ~Iu$^?޵8ٻ:UɄ?W%N~lQrldŸ?b]+2oZt9n';(!潐ɳY߷o>+}3 oͷo|#jF79:*cgɡ;wu>%so_?|\Twμ'ecstE]jR34n~OvxCu#ӕvHuG3e3|F9|qyWouDj%SȘҪ9?Ȼ6`uȑ&eٲd#3xGw9"LXxU: Ę yoʟF ɱd¬r9sGZ~Ù$;-?rsmc2ay`EԻOl7Zt5VdYmYްl#N8/W2$?_rwGw^6gs r[s %^sM3 g;kz ,whz#'gJ޿E?*w37?c|[\dw]3f$rxuӕήy4stAS >曹//-c/K^3;yې];LInj9lsȽ SÇgh#ۧS|UŅwj}sYɧ>Ö[\~sTL4l6aIuu rݗZ eΩJF\}KK~РAG%kk>x7yт]zZ۾w|+o >lH;ݖδs㳳9v;WL8CS]1N `Yuq9}Xt V @;"(p@ g $(p@uD{n%(Um(p@ g $(p@ g $(p@W㯽.CN9n~_{F{;oKG8 j,t\FgZS㯽n`}KCN9{ C  KkU!f}@;Au%Ny3G .5ZkZ^݆UN^Sh34,kͺ6>'hh![JSZگ@EW[\֞lU,mK %hZ4$oݔ SidٚxSV2\M4 < @uS- rǷ4%}Vm *63>-Ж gJV͵YRϻn.iKo6O%l9xm5iNRVڭ f}@Zb@-n%I[cKhJĴ&F4uo T'E8ֆ!E'7g7Wr!h[ "6|mylNTmyL8Мekb48n8o{4V״v^k@ hlA֞>m͹OSTuFQ{4]lkEߚs}ҟQ`R 5*I&Mtۂ: 4$T5H8P @3vbH8P @3H8P @3H8PEwmk- mʐSN) lTΰ3E;vXR9CJr{>>CC=AA}4 g $(p@ g $(p@u,l-S*=[=}ضANڂ:؇rRݿmJ[xhm5ЇӇՔ4 rnՔW@ [; `$(p@ g $(mYhk|&ik|&ik|&iK|iCTH8P ӚZ^ѱܤtִkt>P™ {64v|kCeMؽt>P95* YSeTp6W28)7TpZ"esφO8P @Y™6ualu iM _™lfsA`r]*™V)J fTJՄ3E4δ 5v̴~e@UF 0[`QӚ*5M񡧜UoQr6c6gKϦTZp0l\LcLfa͙ kdSU- S6nsU0MoR5TPs| Lk(p@ g $(p@ g $(pئJl g $(p@ g $(p@u,l-S*URrATf034Hۃ}8*g $(p@ g $(p@ g $(p@ g $(p@ g $(p@ g $(p@۰ryJ@5eͤr6G0CTۃ}8*g $(p@ g $(p@)J)EwI3H8P @3۬rRTt7^ @3H8P @3H8P @:a)*JkI l` S9CMpxTH8P @3H8P @3H8P @3H8P @3H8P @3H8PEwmXsJҚ2fR9#T|bAS>^3H8P @3H8P @3Xt_{Fdžrra7v͖*C8Sa_{]'%_{]ؽt>P95A$[ MT@[T.S*+&U =䍂Ƃ={9L5"esa+=h} g $ڭr\tH8Kvr g GǢ;yغXML+δ ӚZA fZr{R;jEhq 4p, F fh#Lk3 U68>*Z?fLC!iCl֞`SA h}™6hdKU-[s~cLfa͙ ڠaцmek_Tpxlu͹0Ӛ $(p@ g $(p@ g $(p@ g $(p@ g $(p@۰ryJ@5eͤr6G0CTۃ}8*g $(p@ g $(p唚)6@8P @3H8P @3H8P @3H8P @3c`V.oRM(J))y+Ք4 rWH`{ДW@ @3H8P @3H8P @3H8P @3H8P @3H8P @3c`V.oRTf034 P 2)c?xTۜ?p@{+9v܆ `M ,,HEy ̛EC$K #!A( PBB0w}>>R+*WչS.o1H8H8H8kWx&$$$$$$$$$$$$$$$.i淩Hm6R9S3$rTwd#a33333e4MS #$$$݀;yMq)cz =fn{-& dK8sॵ6$iLA1K Μ$E2ݿfzp&Ӆƞ{™Ą @ WkʐE{>3Zƥ\5]P%TRY4U]8p&#S(p p 5]\"9TpO2!+8 qg3!™xͳ+6]i |™Ö*fS p hIeڪvRqS54I dfKHrcH8H8H8H8B]U4xF8H8H8H8H8H8H8H8H8H8di&$#.&$!$$.lJ`qzR9S3$rTwd#a33333333333333334Mgۨ: g g g gY(pȞ| g=D7 [RfNْ"!13l*%c;$$$$$$$$$$$$$$4MtY: I g[ZE @ @ @ @ @ @paK.CZڒlrfHL ۩`G*g g۩j dB8H8H8H8dive@33mb pKLz&nK  @ @ p{֝" g[ M8H8H8H8dik!$$.i深^ E3S׵und8 GHL u%c;azpG +n3@82 g g g g g g=D7N޾~S|z\]n?%u@3' P{n޷f{HI ܝpk~\U˚TgNu:kk8p&̴ƶ=™Ą @ @ @ @ @ @ @ 꺮np3 Ž@@@`1O8H8!|_>=.~ضcHC8!It7aΚ7G]0ʴCeK pAݿfzpƂۧ.`)9g gZS, !к0.u 22D0@Z™L1.u dd*Q=eifr&;? MT53ܓ`Lg.n NC|z\>YrmfLC&3gWlcA+B3-SU0C̦ڪvRqSU0CӛT@ Lf$g=8iM3 Wj8p p$u]WMD7Ȍp p pꛅM8px gAip`'$R5 @!Q;p. 2s ggLs6Tg !Tk\E]I3xπ b=D7 [r2R5p>ﰒ  3j)Ovɵ6sw s@b*g;@1 p%ccvP9u\uyڄ3{ Y JK{ @a\} E8p2 % 3/ǖ{e\HM pn +_' \xy9[> \A*3R4 @n%@G8돂,LpeoMȋpnȰcg sKR+IKt3pq~/ˑg]q -  ]Rep)m[򋝓F*IH dȫ%13l*rL | pA @ ´x?ɑ~ p `RY'\/@\  @62'$$Sz3! LG@h0TߛJrgnv'ySAg9I8p3Nȑ' g 6U !36c{M8p#%ȗpPs=O)0@^32r uw q3iO *U33c g 61\2v7+O8P˯ts!c^dIz^ʗ;w1T\Q'B|91`*KAp1Q9vKN|iC2G Li(ASYx?C,pΗHH8pCah,b ?p휰p-EOnk|8` H}=iL!!W$KO 3/?qS9~47v-Nt'؇qaR^ ZTd~kZ; Mlͺ3g2wI};J0_1zd(iM<6a 3tC*Ӝtlﳫ pA/Jvc]wll}9T1C` @r]m0vbpGW:&a][/*f&]eI/g>c5׫c,Hvwv{\}ޏ >\obZ@&r,] ߰wߥ%K=.v۱\y;5}l-ggn;pom r:6r]c=jLk5Wy S CfckVܑp 5@kC`^#`-45EiũםBNN0Rt4VuQ.c%uXgsh{>_e 'q.Ǿ M_ g:~J~M~_r79`wh(>뵏pvi (pe1ڦڶgWE B'a>C8RvIS8m2!m=a+W9Xp6%{ȠOrIxO$#(K =jR U U,t/WP- G}oU:3P~eKGK]UȚ0p K͵DÌ-? [RKs$oԧ6tѶ\M*Wq]U}||f ]̘EB% w!ڱכPKl6 9; T]M%pX;.3 Mkj]piCck m1mxFlT.ij%uzgm?tC`*f1p%7v bBz:/c6X<>SsM?.Y&pSSM){]z.YUCWZڰpM =_:AAAAm~}m8 Plgl3t?,G z^<=UuUMz] 63I9m̋Dt*gA^~/B[թ$$$$jM@޾~S|z\mnϽKUM=FdJcޖ> 9N:FrWKj3@/֡m_o_ vnϽ۫'Ifoz'nOrȘ}ǖ|o2 6;)}^>=x$K-sZ>;FpKnWs_S\'>uT4(Sǵn{g0kי }k*I쩚io_' 9Pk3@_lPp1B$ܸ5%\1CDIH83䑃a}c !/]pfjJ\pF#o$ !g"8.#dA`Sp%!'#Lk.aj}n[$S}l들vdV1l#3}5 {!}@>OQ}@-.r{C8\ƚ_ضl[>ц\pӷf{}sCגcXwwG~|8w¶v{idk/$GJ}3&5ۤ2Pde駟R35@b?{^~!#Gz; G8LS][ݩxzv@ $255(PejT H3@p iML?΢r L*8.==tP;ۆ}T_lw6 g #c!I 86=Y]o=nhLg@LkFkg"LU\=xO)ў󭭚* @1Xs`gƤO;/ N+C-YD8EM|P8 M8M/2)A9|skX".r~PUUݻ|Zxnv:0]f>6 YJ pe駟H iM3333@, \p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p C'_xZμz(iM }?giMUUU}Y8'|y3}W_}R8-tʫWTd@@@@@3~_Vn @b3_~^~2 gC8H8H8!P?~o?TUZYzT_U??3U;U\ l g_γSC?nw? hY9H8H8H8H8(R~^t3Nr p p p p ЩWkzՙ/™z9KH|'I֜/R<-@q>@~zZcEIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/docs/pyraf_tutorial.rst0000644000175000017500000012312614203121554016113 0ustar00olesoles****************** The PyRAF Tutorial ****************** :Authors: - Richard L. White - Perry Greenfield - Ole Streicher :Abstract: PyRAF, based on the Python scripting language, is a command language for IRAF that can be used in place of the existing IRAF CL. This document provides a tutorial introduction to the PyRAF system, showing how it can be used to run IRAF tasks. It describes how to run IRAF tasks using either the familiar IRAF syntax or Python syntax. It also describes PyRAF’s graphical parameter editor and the graphics system. Introduction ============ Why a New IRAF CL? ------------------ The IRAF_ CL has some shortcomings as a scripting language that make it difficult to use in writing scripts, particularly complex scripts. The major problem is that the IRAF CL has no error or exception handling. If an error occurs, the script halts immediately with an error message that has little or no information about where the error occurred. This makes it difficult to debug scripts, impossible to program around errors that can be anticipated, and difficult to provide useful error messages to the user. But there are other important reasons for wanting to replace the CL. We want to use a command language that is a stronger environment for general programming and that provides more tools than the existing IRAF CL. Python_ is a free, popular scripting language that is useful for a very wide range of applications (e.g., scientific analysis, text processing, file manipulation, graphical programming, etc.). Python is also an open system that can be easily extended by writing new modules in Python, C, or Fortran. By developing a Python interface to IRAF tasks, we open up the IRAF system and make it possible to write scripts that combine IRAF tasks with the numerous other capabilities of Python. Why Should I Use PyRAF? ----------------------- PyRAF can run the vast majority of IRAF tasks without any problems. You can display graphics and use interactive graphics and image display tasks as well. You can even run almost all IRAF CL scripts! While many things work the same way as they do in the IRAF CL, there are some differences in how things are done, usually for the better. The most important difference between PyRAF and the old IRAF CL is that PyRAF allows you to write scripts using a much more powerful language that has capabilities far beyond simply running IRAF tasks. Some of the things that now become possible are: * Easier script debugging. * Ability to trap errors and exceptions. * Access to all the features of Python including: - built-in list (array) and dictionary (hash table) data structures - extensive string handling libraries - GUI libraries that make it straightforward to wrap IRAF tasks with GUI interfaces - web and networking libraries making it easy to generate html, send email, handle cgi requests, etc. * Simple access to other software packages written in C or Fortran. * Ability to read and manipulate FITS files and tables using astropy and numpy. As far as the interactive environment goes, the new system has most of the current features and conveniences of the IRAF CL as well as a number of new features. It is possible to use exactly the same familiar IRAF CL syntax to run IRAF tasks (e.g., ``imhead dev$pix long+``). There are some additional features such as command-history recall and editing using the arrow keys, tab-completion of commands and filenames, a GUI equivalent to the epar parameter editor, the ability to display help in a separate window, and a variety of tools for manipulation of graphics. We believe the new environment is in many ways more convenient for interactive use than the IRAF CL. Prerequisites ------------- This guide will not attempt to serve as a Python introduction, tutorial or guide. There are a number of other documents that serve that role well. At some point we recommend spending a few hours familiarizing yourself with Python basics. Which Python documentation or books to start with depends on your programming background and preferences. Some people will find quick introductions like the `Python tutorial`_ all they need. In any case, do not spend too much time reading before actually starting to run PyRAF and use Python. The Python environment is an interactive one and one can learn quite a bit by just trying different things after a short bit of reading. In fact, you may prefer to try many of the examples given in this guide before reading even a single Python document. We will give enough information for you to get started in trying the examples. Our approach will be largely based on examples. This guide also is not intended to be an introduction to IRAF. We assume that current PyRAF users are relatively experienced with IRAF. Check the `Beginner’s Guide to Using IRAF`_ for an (outdated) introduction. Installation ============ To install PyRAF, it is required to have both IRAF_ and Python_ already installed. For the IRAF installation, it is recommended to use a version from iraf-community_, as these include many bugs fixed for current Linux and macOS versions and architectures. Both a self-compiled and a binary IRAF package (f.e. in Ubuntu_) will work. The IRAF installation should have a properly configured environment, especially the ``iraf`` environment variable must be set to point to the IRAF installation directory (i.e. to ``/usr/lib/iraf/`` on Ubuntu or Debian systems). On multi-arch IRAF installations, the ``IRAFARCH`` environment variable should specify the architecture to use. This is usually already set during the IRAF installation procedure. The minimal Python required for PyRAF is 3.6, but it is recommended to use the latest available version. An installation in an virtual environment like venv_ or conda_ is possible. On some Linux distributions, PyRAF is readily available as a binary package and can be installed with the package installer, like ``sudo apt install python3-pyraf`` on Debian or Ubuntu. On all other systems, the package can be installed via PyPI_ with the command ``pip3 install pyraf``. Note that if no binary installation is available on PyPI, the package requires a compilation, so aside from pip3, the C compiler and development libraries (on Linux ``libx11-dev``) should be installed. Starting and Using PyRAF Interactively ====================================== If PyRAF is installed (and your environment is properly configured), typing ``pyraf`` will start up PyRAF using the front-end interpreter. (There are a few command line options, described in :ref:`Command Line Options`, that can be listed using ``pyraf --help``.) Here is an illustration of starting PyRAF and running some IRAF tasks using the CL emulation syntax. :: $ pyraf NOAO/IRAF community V2.16.1+ snapshot Mon, 24 Aug 2021 10:27:34 +0000 This product includes results achieved by the IRAF64 project in 2006- 2009 directed by Chisato Yamauchi (C-SODA/ISAS/JAXA). Welcome to IRAF. To list the available commands, type ? or ??. To get detailed information about a command, type `help '. To run a command or load a package, type its name. Type `bye' to exit a package, or `logout' to get out of the CL. Visit http://github.com/iraf-community/iraf/issues to report problems. The following commands or packages are currently defined: … --> imheader dev$pix long+ dev$pix[512,512][short]: m51 B 600s No bad pixels, min=-1., max=19936. Line storage mode, physdim [512,512], length of user area 1621 s.u. Created Mon 23:54:13 31-Mar-1997, Last modified Mon 23:54:14 31-Mar-1997 Pixel file "HDR$pix.pix" [ok] ’KPNO-IRAF’ / ’31-03-97’ / IRAF-MAX= 1.993600E4 / DATA MAX IRAF-MIN= -1.000000E0 / DATA MIN IRAF-BPX= 16 / DATA BITS/PIXEL IRAFTYPE= ’SHORT’ / PIXEL TYPE CCDPICNO= 53 / ORIGINAL CCD PICTURE NUMBER … HISTORY ’24-04-87’ HISTORY ’KPNO-IRAF’ / HISTORY ’08-04-92’ / --> imstat dev$pix # IMAGE NPIX MEAN STDDEV MIN MAX dev$pix 262144 108.3 131.3 -1. 19936. --> imcopy dev$pix mycopy.fits dev$pix -> mycopy.fits You may notice a great similarity between the PyRAF login banner and the IRAF login banner. That’s because PyRAF reads your normal :file:`login.cl` file and goes through exactly the same startup steps as IRAF when a session begins. If you have customized your :file:`login.cl` or :file:`loginuser.cl` files to load certain packages, define tasks, etc., then those customizations will also take effect in your PyRAF environment. You can start up PyRAF from any directory; unlike the IRAF CL, you are not required to change to your IRAF home directory. PyRAF determines the location of your IRAF home directory by looking for your :file:`login.cl` file, first in your current working directory and then in a directory named :file:`iraf` in your home directory. So as long as your IRAF home directory is :file:`~/iraf` or :file:`~/.iraf`, you can start up PyRAF from any working directory. (You can start from other directories as well, but without access to :file:`login.cl` your IRAF environment will be only partly initialized. We expect to add a startup configuration file, :file:`.pyrafrc`, that allows you customize your initial PyRAF configuration including your IRAF home directory.) The first time you run PyRAF, it creates a :file:`pyraf` directory in your IRAF home directory. All it stores there is a file named :file:`clcache.sqlite3`, which is used to save translated versions of your own custom CL scripts. Note that the task syntax shown above is identical to that of the IRAF CL. But there is no escaping that you are really running in a Python environment. Should you make a mistake typing a task name, for example, :: --> imstart dev$pix File "", line 1 imstart dev$pix ^ SyntaxError: invalid syntax or should you use other CL-style commands, :: --> =cl.menus File "", line 1 =cl.menus ^ SyntaxError: invalid syntax then you’ll see a Python error message. At this stage, this is the most likely error you will see aside from IRAF-related ones. Aside from some noticeable delays (on startup, loading graphics modules, or in translating CL scripts not previously encountered), there should be little difference between running IRAF tasks in CL emulation mode and running them in the IRAF CL itself. New Capabilities in PyRAF ------------------------- Several capabilities in the PyRAF interpreter make it very convenient for interactive use. The up-arrow key can be used to recall previous commands (no need to type ``ehis``!), and once recalled the left and right arrow keys can be used to edit it. The :kbd:`Ctrl`-:kbd:`R` key does pattern-matching on the history. Just type part of the command (not necessarily at the beginning of the line) and you’ll see the matched command echoed on the command line. Type :kbd:`Ctrl`-:kbd:`R` again to see other matches. Hit return to re-execute a command, or other line-editing keys (left/right arrow, :kbd:`Ctrl`-:kbd:`E`, :kbd:`Ctrl`-:kbd:`A`, etc.) to edit the recalled command. There are many other ways to search and manipulate the history – see the gnu readline documentation for more information. The tab key can be used to complete commands, in a way familiar to users of tcsh and similar shells. At the start of the command line, type ``imhe``:kbd:`Tab` and PyRAF fills in ``imheader``. Then type part of a filename :kbd:`Tab` and PyRAF fills in the rest of the name (or fills in the unambiguous parts and prints a list of alternatives). This can be a great timesaver for those long HST filenames! You can also use tab to complete IRAF task keyword names (e.g., ``imheader lon``:kbd:`Tab` fills in ``longheader``, to which you can add ``=yes`` or something similar). And when using Python syntax (see below), tab can be used to complete Python variable names, object attributes, etc. The function :: saveToFile filename saves the current state of your PyRAF session to a file (including package, task, and IRAF environment variable definitions and the current values of all task parameters). The function :: restoreFromFile filename restores the state of your session from its previously saved state. A save filename can also be given as a Unix command line argument when starting up PyRAF, in which case PyRAF is initialized to the state given in that file. This can be a very useful way both to start up in just the state you want and to reduce the startup time. Differences from the CL and Unimplemented CL Features ----------------------------------------------------- Some differences in behavior between PyRAF and the CL are worth noting. PyRAF uses its own interactive graphics kernel when the CL ``stdgraph`` variable is set to a device handled by the CL itself (e.g., xgterm). If ``stdgraph`` is set to other values (e.g. ``stdplot`` or the imd devices), the appropriate CL task is called to create non-interactive plots. Only the default PyRAF graphics window supports interactive graphics (so you can’t do interactive graphics on image display plots, for example.) Graphics output redirection is not implemented. Some IRAF CL commands have the same names as Python commands; when you use them in PyRAF, you get the Python version. The ones most likely to be encountered by users are :func:`print` and :keyword:`del`. If you want to use the IRAF print command (which should rarely be needed), use ``clPrint`` instead. If you want the IRAF ``delete`` command, just type more of the command (either ``dele`` or ``delete`` will work). Another similar conflict is that when an IRAF task name is identical to a reserved keyword in Python (to see a list, do ``import keyword; print(keyword.kwlist)``), then it is necessary to prepend a ``PY`` (yes, in capital letters) to the IRAF task name. Such conflicts should be relatively rare, but note that :keyword:`lambda` and :keyword:`in` are both Python keywords. The PyRAF help command is a little different than the IRAF version. If given a string argument, it looks up the CL help and uses it if available. For other Python argument types, ``help`` gives information on the variable. E.g., ``help(module)`` gives information on the contents of a module. There are some optional arguments that are useful in Python programs (type ``help(help)`` for more information). If you need to access the standard IRAF help command without the additional PyRAF features, use ``system.help taskname options``. Note that the IRAF help pages are taken directly from IRAF and do not reflect the special characteristics of PyRAF. For example, if you say ``help while``, you get help on the CL while loop rather than the Python while statement. The login message on startup also comes directly from IRAF and may mention features not available (or superseded) in PyRAF. There are a few features of the CL environment and CL scripts that are not yet implemented: Packages cannot be unloaded. There is no way to unload a loaded IRAF package. The bye command exists but does not do anything; the keep command also does nothing (effectively all modifications to loaded tasks and IRAF environment variables are kept). No GOTO statements in CL scripts. Python does not have a :keyword:`goto` statement, so converting CL scripts that use goto’s to Python is difficult. We have made no effort to do such a conversion, so CL scripts with GOTO’s raise exceptions. GOTOs may not ever get implemented. Background execution is not available. Background execution in CL scripts is ignored. Error tracebacks in CL scripts do not print CL line numbers. When errors occur in CL scripts, the error message and traceback refer to the line number in the Python translation of the CL code, not to the original CL code. (If you want to see the Python equivalent to a CL task, use the getCode method – e.g. ``print(iraf.spy.getCode())`` to see the code for the ``spy`` task). The EPAR Parameter Editor ========================= For PyRAF we have written a task parameter editor that is similar to the IRAF epar function but that uses a graphical user interface (GUI) rather than a terminal-based interface. PyRAF’s EPAR has some features not available in the IRAF CL, including a file browser for selecting filename parameters and a pop-up window with help on the task. Upon being invoked in the usual manner in IRAF CL emulation mode, an EPAR window for the named task appears on the screen: :: --> epar ccdlist An EPAR window consists of a menu bar, current package and task information, action buttons, the parameter editing panel, and a status box. If there are more parameters than can fit in the displayed window, they will appear in a scrolling region. .. image:: epar.png Action Buttons -------------- The EPAR action buttons are: Execute Execute the task with parameter values currently displayed in the EPAR windows. (Several windows may be open at once if the task has PSET parameters – see below.) If parameter values were changed and not saved (via the Save button), these new values are automatically verified and saved before the execution of the task. The EPAR window (and any child windows) is closed, and the EPAR session ends. Save This button saves the parameter values associated with the current EPAR window. If the window is a child, the child EPAR window closes. If the window is the parent, the window closes and the EPAR session ends. The task is not executed. Unlearn This button resets all parameters in the current EPAR window to their system default values. Cancel This button exits the current EPAR session without saving any modified parameter values. Parameters revert to the values they had before EPAR was started; the exception is that PSET changes are retained if PSETs were editted and explicitly saved. Task Help This button displays the IRAF help information for a task. Menu Bar -------- The EPAR menu bar consists of **File**, **Options**, and **Help** menus. All of the **File** menu choices map directly to the action button functionality. The **Options** menu allows the user to choose the way help pages are displayed; the information can be directed to the user’s web browser or to a pop-up window (the default). The **Help** menu gives access to both the IRAF task help and information on the operation of EPAR itself. Parameter Editing Panel ----------------------- Different means are used to set different parameter types. Numeric and string parameters use ordinary entry boxes. Parameters with an enumerated list of allowed values use choice lists. Booleans are selected using radio buttons. PSETs are represented by a button that when clicked brings up a new EPAR window. PSET windows and the parent parameter windows can be edited concurrently (you do not have to close the child window to make further changes in the parent window). Parameters may be editted using the usual mouse operations (select choices from pop-up menus, click to type in entry boxes, and so on.) It is also possible to edit parameter without the mouse at all, using only the keyboard. When the editor starts, the first parameter is selected. To select another parameter, use the :kbd:`Tab` or :kbd:`Return` key (:kbd:`Shift`-:kbd:`Tab` or :kbd:`Shift`-:kbd:`Return` to go backwards) to move the focus from item to item. The :kbd:`↑` and :kbd:`↓` arrow keys also move between fields. Use the :kbd:`space` bar to “push” buttons, activate pop-up menus, and toggle boolean values. The toolbar buttons are also accessible from the keyboard using the :kbd:`Tab` and :kbd:`Shift`-:kbd:`Tab` keys. They are located in sequence before the first parameter and after the last parameter (since the item order wraps around at the end.) 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. See the **Help→Epar Help** menu item for more information on keyboard shortcuts. Parameters entered using entry boxes (strings and numbers) are checked for correctness when the focus shifts to another parameter (either via the :kbd:`Tab` key or the mouse.) The parameter values are also checked when either the Save or Execute button is clicked. Any resulting errors are either displayed in the status area at the bottom (upon validation after return or tab) or in a pop-up window (for Save/Execute validation). For parameters other than PSETs, the user can click the right-most mouse button within the entry box or choice list to generate a pop-up menu. The menu includes options to invoke a file browser, clear the entry box, or unlearn the specific parameter value. “Clear” removes the current value in the entry, making the parameter undefined. “Unlearn” restores the system default value for this specific parameter only. The file browser pops up an independent window that allows the user to examine the directory structure and to choose a filename for the entry. 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.) Status Line ----------- Finally, the bottom portion of the EPAR GUI is a status line that displays help information for the action buttons and error messages generated when the parameter values are checked for validity. PyRAF Graphics and Image Display ================================ PyRAF has its own built-in graphics kernel to handle interactive IRAF graphics. Graphics tasks can be run from any terminal window — there is no need to use the IRAF xgterm. If the value for stdgraph set in your login.cl would have the IRAF CL use its built-in graphics kernel, in PyRAF it will use PyRAF’s built-in kernel. The PyRAF kernel is not identical to IRAF’s but offers much the same functionality — it lacks some features but adds others. If you specify a device that uses other IRAF graphics kernels (e.g., for printers or image display plots), PyRAF will use the IRAF graphics kernel to render those plots. There are some limitations when using IRAF kernels. For example, it is not possible to use interactive graphics tasks with those kernels. But otherwise, most of their functionality is available. The PyRAF built-in graphics kernel is based on OpenGL and Tkinter. Graphics windows are created from PyRAF directly. One can run a graphics task like any other. For example, typing :: --> prow dev$pix 256 will create a graphics window and render a plot in it. The graphics window is responsive at all times, not just while an IRAF task is in interactive graphics mode. If the window is resized, the plot is redrawn to fit the new window. There is a menu bar with commands allowing previous plots to be recalled, printed, saved, etc. The **Edit→Undo** menu command can remove certain graphics elements (e.g., text annotations and cursor marks.) It is possible to create multiple graphics windows and switch between them using the **Window** menu. See the **Help** menu for more information on the capabilities of the PyRAF graphics window. Some options (such as the default colors) are easily configurable. .. image:: prow.png Interactive graphics capability is also available. For example, typing ``implot dev$pix`` will put the user into interactive graphics mode. The usual graphics keystroke (gcur) commands recognized by the task will work (e.g., lowercase letter commands such as c) and colon commands will work as they do in IRAF. Most CL-level (capital letter) keystroke commands have not yet been implemented; the following CL level commands are available: * The arrow keys move the interactive cursor one pixel. :kbd:`Shift` combined with the arrow keys moves the cursor 5 pixels. * **C** prints the current cursor position on the status line. * **I** immediately interrupts the task (this is the gcur equivalent to control-C). * **R** redraws the plot with annotations removed (also available through the **Edit→Undo All** menu item.) * **T** annotates the plot at the current cursor position, using a dialog box to enter the text. * **U** undoes the last “edit” to a plot (annotations or cursor markers). This can be repeated until only the original plot remains. (Also available using **Edit→Undo**.) * A colon (**:**) prompts on the status line for the rest of the colon command. Other input from interactive graphics tasks may also be done from the status line. * The **:.markcur** directive is recognized. It toggles the cursor marking mode (**:.markcur+** enables it, **:.markcur-** disables it). This directive cannot be abbreviated. Help for interactive IRAF tasks can usually be invoked by typing **?**; the output appears in the terminal window. Output produced while in cursor-mode (e.g., readouts of the cursor position) appear on the status line at the bottom of the graphics window. Note that the status line has scrollbars allowing previous output to be recalled. PyRAF attempts to manipulate the window focus and the cursor location in a sensible way. For example, if you start an interactive graphics task, the mouse position and focus are automatically transferred to the graphics window. If the task does not appear to be responding to your keyboard input check to see that the window focus is on the window expecting input. Printing Graphics Hardcopy -------------------------- It is possible to generate hard copy of the plotted display by using the **File→Print** menu item or, in gcur mode, the equal-sign (=) key. PyRAF will use the current value of stdplot as the device to plot to for hardcopy. Inside scripts, a hardcopy can be printed by :: --> from pyraf.gki import printPlot # only need this once per session --> printPlot() This could be used in a Python script that generates graphics using IRAF tasks. It is also possible to do other graphics manipulations in a script, e.g., changing the display page. Multiple Graphics Windows ------------------------- It is possible to display several graphics windows simultaneously. The **Window→New** menu item can create windows, and the **Window** menu can also be used to select an existing window to be the active graphics window. Windows can be destroyed using the **File→Quit Window** menu item or directly using the facilities of the desktop window manager (close boxes, frame menus, etc.) It is also possible to create new windows from inside scripts. If you type: :: --> from pyraf import gwm # only need this once per session --> gwm.window("My Special Graphic") you will create a new graphics window which becomes the current plotting window for PyRAF graphics. The :func:`gwm.window()` function makes the named window the active graphics window. If a graphics window with that name does not yet exist, a new one is created. Windows can be deleted by closing them directly or using :func:`gwm.delete()`. Using these commands, one can write a script to display several plots simultaneously on your workstation. Other Graphics Devices ---------------------- To plot to standard IRAF graphics devices such as xterm or xgterm one can :: --> set stdgraph = stgkern --> iraf.stdgraph.device = "xgterm" or whatever device you wish to use. [Note the Python version of the set statement is ``iraf.set(stdgraph="stgkern")``]. In this way it is possible to generate plots from a remote graphics terminal without an Xwindows display. The drawback is that is is not possible to run interactive graphics tasks (e.g., ``implot`` or ``splot``) using this approach. It may be necessary to call :func:`iraf.gflush()` to get the plot to appear. One can generate plots to other devices simply by setting ``stdgraph`` to the appropriate device name (e.g., ``imdr`` or ``stdplot``). Only special IRAF-handled devices such as ``xgterm`` and ``xterm`` need to use the “magic” value ``stgkern`` for ``stdgraph``. IRAF tasks such as ``tv.display`` that use the standard image display servers (**ximtool**, **SAOImageDS9**) should work fine. Interactive image display tasks such as ``imexamine`` work as well (as long as you are using the PyRAF graphics window for plotting). Graphics output to the image display (allowing plots to overlay the image) is supported only through the IRAF kernel, but a PyRAF built-in kernel is under development. Running Tasks in Python Mode ============================ If that’s all there was to it, using PyRAF would be very simple. But we would be missing much of the point of using it, because from CL emulation mode we can’t access many of the powerful programming features of the Python language. CL emulation mode may be comfortable for IRAF users, but when the time comes to write new scripts you should learn how to run IRAF tasks using native Python syntax. There are a number of ways of running tasks and setting parameters. The system is still under development, and depending on user feedback, we may decide to eliminate some of them. Below we identify our preferred methods, which we do not intend to eliminate and which we recommend for writing scripts. The PyRAF Interpreter Environment --------------------------------- When the PyRAF system is started using the **pyraf** command as described previously, the user’s commands are actually being passed to an enhanced interpreter environment that allows use of IRAF CL emulation and provides other capabilities beyond those provided by the standard Python interpreter. In fact, when **pyraf** is typed, a special interpreter is run which is a front end to the Python interpreter. This front-end interpreter handles the translation of CL syntax to Python, command logging, filename completion, shell escapes and the like which are not available in the default Python interpreter. It is also possible to use PyRAF from a standard Python session, which is typically started by simply typing **python3** at the Unix shell prompt. In that case the simple CL syntax for calling tasks is not available and tab-completion, logging, etc., are not active. For interactive use, the conveniences that come with the PyRAF interpreter are valuable and we expect that most users will use PyRAF in this mode. One important thing to understand is that the alternate syntax supported by the PyRAF front end interpreter is provided purely for interactive convenience. When such input is logged, it is logged in its translated, Python form. Scripts should always use the normal Python form of the syntax. The advantage of this requirement is that such scripts need no preprocessing to be executed by Python, and so they can be freely mixed with any other Python programs. In summary, if one runs PyRAF in its default mode, the short-cut syntax can be used; but when PyRAF is being used from scripts or from the standard Python interpreter, one must use standard Python syntax (not CL-like syntax) to run IRAF tasks. Even in Python mode, task and parameter names can be abbreviated and, for the most part, the minimum matching used by IRAF still applies. As described above, when an IRAF task name is identical to a reserved keyword in Python, it is necessary to prepend a ``PY`` to the IRAF task name (i.e., use ``iraf.PYlambda``, not ``iraf.lambda``). In Python mode, when task parameters conflict with keywords, they must be similarly modified. The statement ``iraf.imcalc(in="filename")`` will generate a syntax error and must be changed either to ``iraf.imcalc(PYin="filename")`` or to ``iraf.imcalc(input="filename")``. This keyword/parameter conflict is handled automatically in CL emulation mode. Some of the differences between the PyRAF interpreter and the regular Python interpreter besides the availability of CL emulation mode: ====================== ============================ ============================= command PyRAF interpreter Python default interpreter ====================== ============================ ============================= prompt ``-->`` ``>>>`` print interpreter help ``.help`` n/a exit interpreter ``.exit`` EOF (:kbd:`Ctrl`-:kbd:`D`) or ``sys.exit()`` start logging input ``.logfile`` filename n/a append to log file ``.logfile`` filename append n/a stop logging input ``.logfile`` n/a run system command ``!`` command ``os.system(’command’)`` start a subshell ``!!`` ``os.system(’/bin/sh’)`` ====================== ============================ ============================= Example with Standard Python Syntax ----------------------------------- This example mirrors the sequence for the example given above in the discussion of CL emulation (:ref:`Starting and Using PyRAF Interactively`). In the discussion that follows we explain and illustrate some variants. :: $ pyraf … --> iraf.imheader("dev$pix", long=yes) dev$pix[512,512][short]: m51 B 600s No bad pixels, min=-1., max=19936. Line storage mode, physdim [512,512], length of user area 1621 s.u. Created Mon 23:54:13 31-Mar-1997, Last modified Mon 23:54:14 31-Mar-1997 Pixel file "HDR$pix.pix" [ok] ’KPNO-IRAF’ / ’31-03-97’ / IRAF-MAX= 1.993600E4 / DATA MAX IRAF-MIN= -1.000000E0 / DATA MIN IRAF-BPX= 16 / DATA BITS/PIXEL IRAFTYPE= ’SHORT’ / PIXEL TYPE CCDPICNO= 53 / ORIGINAL CCD PICTURE NUMBER … HISTORY ’24-04-87’ HISTORY ’KPNO-IRAF’ / HISTORY ’08-04-92’ / --> iraf.imstat("dev$pix") # IMAGE NPIX MEAN STDDEV MIN MAX dev$pix 262144 108.3 131.3 -1. 19936. --> imcopy("dev$pix", "mycopy.fits") dev$pix -> mycopy.fits The mapping of IRAF CL syntax to Python syntax is generally quite straightforward. The most notable requirements are: * The task or package name must be prefixed with ``iraf.`` because they come from the iraf module. In scripts, use :: from pyraf import iraf to load the iraf module. Note that the first time PyRAF is imported, the normal IRAF startup process is executed. It is also possible to import tasks and packages directly using :: from pyraf.iraf import noao, onedspec With this approach, packages are automatically loaded if necessary and tasks can be used without the iraf. prefix. Like the IRAF CL, packages must be loaded for tasks to be accessible. * The task must be invoked as a function in Python syntax (i.e., parentheses are needed around the argument list). Note that parentheses are required even if the task has no arguments – e.g., use ``iraf.fitsio()``, not just ``iraf.fitsio``. * String arguments such as filenames must be quoted. Another change is that boolean keywords cannot be set using appended ``+`` or ``-`` symbols. Instead, it is necessary to use the more verbose ``keyword=value`` form (e.g., ``long=yes`` in the example above). We have defined Python variables ``yes`` and ``no`` for convenience, but you can also simply say ``long=True`` to set the (abbreviated) ``longheader`` keyword to true. Emulating pipes in Python mode is also relatively simple. If a parameter ``Stdout=True`` is passed to a task, the task output is captured and returned as a list of Python strings, with one string for each line of output. This list can then be processed using Pythons sophisticated string utilities, or it can be passed to another task using the Stdin parameter: :: --> s = iraf.imhead("dev$pix", long=yes, Stdout=1) --> print(s[0]) dev$pix[512,512][short]: m51 B 600s --> iraf.head(nl=3, Stdin=s) dev$pix[512,512][short]: m51 B 600s No bad pixels, min=-1., max=19936. Line storage mode, physdim [512,512], length of user area 1621 s.u. ``Stdin`` and ``Stdout`` can also be set to a filename or a Python filehandle object to redirect output to or from a file. ``Stderr`` is also available for redirection. Note the capital ``S`` in these names – it is used to eliminate possible conflicts with task parameter names. Setting IRAF Task or Package Parameters --------------------------------------- There are multiple ways of setting parameters. The most familiar is simply to provide parameters as positional arguments to the task function. For example :: --> iraf.imcopy("dev$pix", "mycopy.fits") Alternatively, one can set the same parameters using keyword syntax: :: --> iraf.imcopy(input="dev$pix", output="mycopy.fits") Hidden parameters can only be set in the argument list this way (analogous to IRAF). As in the IRAF CL, the parameter values are learned for non-hidden parameters (depending on the mode parameter settings) but are not learned (i.e., are not persistent) for hidden parameters. But parameters can also be set by setting task attributes. For example: :: --> iraf.imcopy.input = "dev$pix" --> iraf.imcopy.output = "mycopy.fits" --> iraf.imcopy() # run the task with the new values These attribute names can be abbreviated (don’t expect this behavior for most Python objects, it is special for IRAF task objects): :: --> iraf.imcopy.inp = "dev$pix" --> iraf.imcopy.o = "mycopy.fits" PyRAF is flexible about the types used to specify the parameter so long as the conversion is sensible. For example, one can specify a floating point parameter in any of the following ways: :: --> iraf.imstat.binwidth = "33.0" --> iraf.imstat.binwidth = "33" --> iraf.imstat.bin = 33.0 --> iraf.imstat.bin = 33 but if the following is typed: :: --> iraf.imstat.bin = "cow" Traceback (innermost last): File "", line 1, in ? ValueError: Illegal float value ’cow’ for parameter binwidth An error traceback results. When running in the PyRAF interpreter, a simplified version of the traceback is shown that omits functions that are part of the pyraf package. The ``.fulltraceback`` command (which can be abbreviated as can all the executive commands) will print the entire detailed traceback; it will probably only be needed for PyRAF system developers. Python tracebacks can initially appear confusing, but they are very informative once you learn to read them. The entire stack of function calls is shown from top to bottom, with the most recently called function (where the error occurred) listed last. The line numbers and lines of Python code that generated the error are also given. One can list the parameters for a task using one of the following commands (in addition to the usual IRAF ``lpar imcopy``): :: --> iraf.imcopy.lParam() --> iraf.lpar(iraf.imcopy) # Note there are no quotation marks --> iraf.lpar(’imcopy’) For those who have encountered object-oriented programming, ``iraf.imcopy`` is an ’IRAF task object’ that has a method named ``lParam`` that lists task parameters. On the other hand, ``iraf.lpar`` is a function (in the iraf module) that takes either an IRAF task object or a string name of a task as a parameter. It finds the task object and invokes the ``.lParam()`` method. One can start the EPAR utility for the task using a parallel set of commands: :: --> iraf.imcopy.eParam() --> iraf.epar(iraf.imcopy) --> iraf.epar(’imcopy’) Tasks appear as attributes of packages, with nested packages also found. For example, if you load the ``noao`` package and the ``onedspec`` subpackage, then the ``identify`` task can be accessed through several different means: ``iraf.identify``, ``iraf.noao.identify``, or ``iraf.onedspec.identfy`` will all work. Ordinarily the simple ``iraf.identify`` is used, but if tasks with the same name appear in different packages, it may be necessary to add a package name to ensure the proper version of the task is found. Other Ways of Running IRAF Tasks -------------------------------- One way of reducing the typing burden (interactively or in scripts, though perhaps it isn’t such a good idea for scripts) is to define an alias for the iraf module after it is loaded. One can simply type: :: --> i = iraf --> i.imcopy("dev$pix", "mycopy.fits") --> i.imstat("mycopy.fits") But don’t use i for a counter variable and then try doing the same! E.g., :: --> i = 1 --> i.imcopy(’dev$pix’,’mycopy.fits’) will give you the following error message AttributeError: :: ’int’ object has no attribute ’imcopy’ since the integer `1` has no imcopy attribute. Command Line Options ==================== There are a few command-line options available for PyRAF: -h List the available options. There are long versions of some options (e.g., ``--help`` instead of ``-h``) which are also described. -c command Command passed in as string (any valid PyRAF command) -e Turn on ECL mode -m Run the PyRAF command line interpreter to provide extra capabilities (default) -i Do not start the special PyRAF interpreter; just run a standard Python interactive session -n No splash screen during startup -s Silent initialization (does not print startup messages) -v Set verbosity (repeated ’v’ increases the level; mainly useful for system debugging) -x No graphics will be attempted/loaded during session -y Run the IPython shell instead of the normal PyRAF command shell (only if IPython is installed) A save filename (see :ref:`New Capabilities in PyRAF`) can be given as a command line argument when starting up PyRAF, in which case PyRAF is initialized to the state given in that file. This allows you to start up in a particular state (preserving the packages, tasks, and variables that have been defined) and also reduces the startup time. .. _Python: https://www.python.org/ .. _Python tutorial: https://docs.python.org/3/tutorial/ .. _venv: https://docs.python.org/3/library/venv.html .. _conda: https://docs.conda.io/ .. _PyPI: https://pypi.org/project/pyraf .. _IRAF: https://iraf-community.github.io .. _iraf-community: https://iraf-community.github.io .. _Beginner’s Guide to Using IRAF: https://iraf-community.github.io/doc/beguide.pdf .. _Ubuntu: https://www.ubuntu.com/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/docs/teal_guide.rst0000644000175000017500000005575214214070451015163 0ustar00olesolesCookbook for Building TEAL Interfaces ===================================== Authors: Warren J. Hack, Chris Sontag, Pey Lian Lim Date: January 30, 2014 The release of the Task Editor And Launcher(TEAL) with STScI_Python v2.10 in June 2010 provided the tools for building powerful GUI interfaces for editing the parameters of complex tasks and running those tasks with minimal effort. Learning how to use something new always takes a special effort, and this document provides a step-by-step walkthrough of how to build TEAL interfaces for any Python task to make this effort as easy as possible. Introduction ------------ The new TEAL GUI can be added to nearly any Python task that allows users to set parameters to control the operation of the task. Adding a TEAL interface to a Python task requires some minor updates to the task's code in order to allow TEAL to create and control the GUI for setting all the necessary parameters. TEAL itself relies on the `ConfigObj module`_ for the basic parameter handling functions, with additional commands for implementing enhanced logic for controlling the GUI itself based on parameter values. The GUI not only guides the user in setting the parameters, but also provides the capability to load and save parameter sets and the ability to read a help file while still editing the parameters. The interface to TEAL can also be set up alongside a command-line interface to the task. This document provides the basic information necessary for implementing a TEAL interface for nearly any Python task to take full advantage of the control it provides the user in setting the task parameters. This document does not assume the user has any familiarity with using configobj in any manner and as as result includes very basic information which developers with some experience with configobj can simply skip over. The development of the TEAL interface for the task `resetbits` in the `betadrizzle` package is used as an example. More elaborate examples will be explained after the development of the TEAL interface for `resetbits` has been described. Building the Interface ---------------------- The order of operations provided by this document is not the only order in which these steps can be performed. This order starts with the simplest operation then leads the developer into what needs to be done next with the least amount of iteration. Step 1: Defining the Parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The primary purpose for developing a TEAL interface is to provide a GUI which can be used to set the values for the task's parameters. This requires that the developer identify the full set of task parameters which the user will be required to provide when running the task. The signature for the task `resetbits` is:: def reset_dq_bits(input,bits,extver=None,extname='dq') These parameters now have to be described in a pair of configobj parameter files in order to define the parameters, their types and any validation that may need to be performed on the input values. Default Values for the Parameters """"""""""""""""""""""""""""""""" The first file which needs to be defined provides the default values for each parameter. Default values can be any string or numerical value, including "" or None. This task will simply need:: _task_name_ = resetbits input = "*flt.fits" bits = 4096 extver = None extname = "dq" The first line tells TEAL what task should be associated with this file. The default values for `extver` and `extname` simply match the defaults provided in the function signature. No default values were required for the other parameters, but these values were provided to support the most common usage of this task. This file needs to be saved with a filename extension of `.cfg` in a `pars/` subdirectory of the task's package. For `resetbits`, this file would be saved in the installation directory as the file:: betadrizzle/lib/pars/resetbits.cfg This file will then get installed in the directory `betadrizzle/pars/resetbits.cfg` with the instructions on how to set that up coming in the last step of this process. Parameter Validation Rules """""""""""""""""""""""""" The type for the parameter values, along with the definition of any range of valid values, is defined in the second configobj file: the configobj specification (configspec) file or `cfgspc` file. This file can also provide rules for how the GUI should respond to input values as well, turning the TEAL GUI into an active assistant for the user when editing large or complex sets of parameters. For this example, we have a very basic set of parameters to define without any advance logic required. TEAL provides validators for a wide range of parameter types, including: * `strings`: matches any string Defined using `string_kw()` * `integer`: matches any integer when a value is always required Defined using `integer_kw()` * `integer` or `None`: matches any integer or a value of None Defined using `integer_or_none_kw()` * `float`: matches any floating point value, when a value is always required Defined using `float_kw()` * `float` or `None`: matches any floating point value or a value of None Defined using `float_or_none_kw()` * `boolean`: matches boolean values - ``True`` or ``False`` Defined using `boolean_kw()` * `option`: matches only those values provided in the list of valid options Defined using `option_kw()` command with the list of valid values as a parameter ConfigObj also has support for IP addresses as input parameters, and lists or tuples of any of these basic parameter types. Information on how to use those types, though, can be found within the `ConfigObj module`_ documentation. With these available parameter types in mind, the parameters for the task can be defined in the configspec file. For the `resetbits` task, we would need:: _task_name_ = string_kw(default="resetbits") input = string_kw(default="*flt.fits", comment="Input files (name, suffix, or @list)") bits = integer_kw(default=4096, comment="Bit value in array to be reset to 0") extver = integer_or_none_kw(default=None, comment="EXTVER for arrays to be reset") extname = string_kw(default="dq", comment= "EXTNAME for arrays to be reset") mode = string_kw(default="all") Each of these parameter types includes a description of the parameter as the `comment` parameter, while default values can also be set using the `default` parameter value. This configspec file would then need to be saved alongside the .cfg file we just created as:: betadrizzle/lib/pars/resetbits.cfgspc .. note:: If you find that you need or want to add logic to have the GUI respond to various parameter inputs, this can always be added later by updating the parameter definitions in this file. A more advanced example demonstrating how this can be done is provided in later sections. Step 2: TEAL Functions for the Task ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TEAL requires that a couple of functions be defined within the task in order for the GUI to know how to get the help for the task and to run the task. The functions that need to be defined are: * ``run(configObj)`` This function serves as the hook to allow the GUI to run the task * ``getHelpAsString()`` This function returns a long string which provides the help for the task The sole input from TEAL will be a ConfigObj instance, a class which provides all the input parameters and their values after validation by the configobj validators. This instance gets passed by TEAL to the task's ``run()`` function and needs to be interpreted by that function in order to run the task. .. note:: The ``run()`` and ``getHelpAsString()`` functions, along with the task's primary user interface function, all need to be in the module with the same name as the task, as TEAL finds the task by importing the taskname. Or, these two functions may instead be arranged as methods of a task class, if desired. Defining the Help String """""""""""""""""""""""" The help information presented by the TEAL GUI comes from the ``getHelpAsString()`` function and gets displayed in a simple ASCII window. The definition of this function can rely on help information included in the source code as docstrings or from an entirely separate file for tasks which have a large number of parameters or require long explanations to understand how to use the task. The example from the `resetbits` task was simply:: def getHelpAsString(): helpString = 'resetbits Version '+__version__+'\n' helpString += __doc__+'\n' return helpString This function simply relies on the module level docstring to describe how to use this task, since it is a simple enough task with only a small number of parameters. .. note:: The formatting for the docstrings or help files read in by this function can use the numpy documentation restructured text markup format to be compatible with Sphinx when automatically generating documentation on this task using Sphinx. The numpy extension results in simple enough formatting that works well in the TEAL Help window without requiring any translation. More information on this format can be found in the `Numpy Documentation`_ pages. More complex tasks may require the documentation which would be too long to comfortably fit within docstrings in the code itself. In those cases, separate files with extended discussions formatted using the numpy restructured text (reST) markup can be written and saved using the naming convention of ```.help``` in the same directory as the module. The function can then simply use Python file operations to read it in as a list of strings which are concatenated together and passed along as the output. This operation has been made extremely simple through the definition of a new function within the TEAL package; namely, ``teal.getHelpFileAsString()``. An example of how this could be used to extend the help file for `resetbits` would be:: def getHelpAsString(): helpString = 'resetbits Version '+__version__+__vdate__+'\n' helpString += __doc__+'\n' helpString += teal.getHelpFileAsString(__taskname__,__file__) return helpString The parameter ``__taskname__`` should already have been defined for the task and gets used to find the file on disk with the name ``__taskname__.help``. The parameter ``__file__`` specifies where the task's module has been installed with the assumption that the help file has been installed in the same directory. The task `betadrizzle` uses separate files and can be used as an example of how this can be implemented. Defining How to Run the Task """""""""""""""""""""""""""" The ConfigObj instance passed by TEAL into the module needs to be interpreted and used to run the application. There are a couple of different models which can be used to define the interface between the ``run()`` function and the task's primary user interface function (i.e. how it would be called in a script). #. The ``run()`` function interprets the ConfigObj instance and calls the user interface function. This works well for tasks which have a small number of parameters. #. The ``run()`` function serves as the primary driver for the task and a separate function gets defined to provide a simpler interface for the user to call interactively. This works well for tasks which have a large number of parameters or sets of parameters defined in the ConfigObj interface. Our simple example for the task ``resetbits`` uses the first model, since it only has the 4 parameters as input. The ``run()`` function can simply be defined in this case as:: def run(configobj=None): ''' Teal interface for running this code. ''' reset_dq_bits(configobj['input'],configobj['bits'], extver=configobj['extver'],extname=configobj['extname']) def reset_dq_bits(input,bits,extver=None,extname='dq'): Interactive use of this function would use the function ``reset_dq_bits()``. The TEAL interface would pass the parameter values in through the run function to parse out the parameters and send it to that same function as if it were run interactively. Step 3: Advertising TEAL-enabled Tasks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Any task which has a TEAL interface implemented can be advertised to users of the package through the use of a ``teal`` function: ``teal.print_tasknames()``. This function call can be added to the package's `__init__.py` module so that everytime the package gets imported, or reloaded, interactively, it will print out a message listing all the tasks which have TEAL GUI's available for use. This listing will not be printed out when importing the package from another task. The `__init__.py` module for the `betadrizzle` package has the following lines:: # These lines allow TEAL to print out the names of TEAL-enabled tasks # upon importing this package. from stsci.tools import teal teal.print_tasknames(__name__, os.path.dirname(__file__)) Step 4: Setting Up Installation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The additional files which have been added to the package with the task now need to be installed alongside the module for the task. Packages in the `STScI_Python` release get installed using Python's `distutils` mechanisms defined through the ``defsetup.py`` module. This file includes a dictionary for `setupargs` that describe the package and the files which need to be installed. This needs to be updated to include all the new files as ``data_files`` by adding the following line to the ``setupargs`` dictionary definition:: 'data_files': [(pkg+"/pars",['lib/pars/*']),( pkg, ['lib/*.help'])], This will add the ConfigObj files in the `pars/` directory to the package while copying any ``.help`` files that were added to the same directory as the module. Step 5: Testing the GUI ^^^^^^^^^^^^^^^^^^^^^^^ Upon installing the new code, the TEAL interface will be available for the task. There are a couple of ways of starting the GUI along with a way to grab the ConfigObj instance directly without starting up the GUI at all. Fundamentally, TEAL is a Python GUI that can be run interactively under any Python interpreter. It can be called for our example task using the syntax:: >>> from stsci.tools import teal >>> cobj = teal.teal('resetbits') Getting the ConfigObj Without Starting the GUI """""""""""""""""""""""""""""""""""""""""""""" The function for starting the TEAL GUI, ``teal.teal()``, has a parameter to control whether or not to start the GUI at all. The ConfigObj instance can be returned for the task without starting the GUI by using the `loadOnly` parameter. For our example task, we would use the command:: >>> cobj = teal.teal('resetbits',loadOnly=True) The output variable `cobj` can then be passed along or examined depending on what needs to be done at the time. Advanced Topics --------------- The topics presented here describe how to take advantage of some of TEAL's more advanced functions for controlling the behavior of the GUI and for working with complex sets of parameters. Most of the examples for these advanced topics use the ConfgObj files and code defined for betadrizzle. Parameter Sections ^^^^^^^^^^^^^^^^^^ The ConfigObj specification allows for parameters to be organized into sections of related parameters. The parameters defined in these sections remain together in a single dictionary within the ConfigObj instance so that they can be passed into tasks or interpreted as a single unit. Use of sections within TEAL provides for the opportunity to control the GUI's behaviors based on whether or not the parameters in a given section need to be edited by the user. A parameter section can be defined simply by providing a title using the following syntax in both the .cfg and .cfgspc files:: [] In betadrizzle, multiple sections are defined within the parameter interface. One section has been defined in the .cfg file as:: [STEP 1: STATIC MASK] static = True static_sig = 4.0 The .cfgspc definition for this section was specified as:: [STEP 1: STATIC MASK ] static = boolean_kw(default=True, triggers='_section_switch_', comment="Create static bad-pixel mask from the data?") static_sig = float_kw(default=4.0, comment= "Sigma*rms below mode to clip for static mask") These two sets of definitions work together to define the 'STEP 1: STATIC MASK' parameter section within the ConfigObj instance. A program can then access the parameters in that section using the name of the section as the index in the ConfigObj instance. The `static` and `static_sig` parameters would be accessed as:: >>> cobj = teal.teal('betadrizzle',loadOnly=True) >>> step1 = cobj['STEP 1: STATIC MASK'] >>> step1 {'static': True, 'static_sig': 4.0} >>> step1['static'] True Section Triggers ^^^^^^^^^^^^^^^^ The behavior of the TEAL GUI can be controlled for each section in a number of ways, primarily as variations on the behavior of turning off the ability to edit the parameters in a section based on another parameters value. A section parameter can be defined to allow the user to explicitly specify whether or not they need to work with those parameters. This can the control whether or not the remainder of the parameters are editable through the use of the `triggers` argument in the .cfgspc file for the section parameter. The supported values for the `triggers` argument currently understood by TEAL are: * ``_section_switch_``: Activates/Deactivates the ability to edit the values of the parameters in this section * ``_rule<#>_``: Runs the code in this rule (defined elsewhere in the .cfgspc file) to automatically set this parameter, and control the behavior of other parameters like section defintions as well. The example for defining the section 'STEP 1: STATIC MASK' illustrates how to use the ``_section_switch_`` trigger to control the editing of the parameters in that section. Another argument defined as ``is_set_by="_rule<#>"`` allows the user to define when this section trigger can be set by other parameters using code and logic provided by the user. The value, ``_rule<#>_`` refers to code in the specified rule (defined at the end of the `.cfgspc` file) to determine what to do. The code which will be run must be found in the configspec file itself, although that code could reference other packages which are already installed. Use of Rules ^^^^^^^^^^^^ A special section can be appended to the end of the ConfigObj files (.cfg and .cfgspc files) to define rules which can implement nearly arbitrary code to determine how the GUI should treat parameter sections or even individual parameter settings. The return value for a rule should always be a boolean value that can be used in the logic of setting parameter values. This capability has been implemented in `betadrizzle` to control whether or not whole sections of parameters are even editable (used) to safeguard the user from performing steps which need more than 1 input when only 1 input is provided. The use of the ``_rule<#>_`` trigger can be seen in the `betadrizzle` .cfgspc file:: _task_name_ = string_kw(default="betadrizzle") input = string_kw(default="*flt.fits", triggers='_rule1_', comment="Input files (name, suffix, or @list)") <other parameters removed...> [STEP 3: DRIZZLE SEPARATE IMAGES] driz_separate = boolean_kw(default=True, triggers='_section_switch_', is_set_by='_rule1_', comment= "Drizzle onto separate output images?") driz_sep_outnx = float_or_none_kw(default=None, comment="Size of separate output frame's X-axis (pixels)" ) <more parameters removed, until we get to the end of the file...> [ _RULES_ ] _rule1_ = string_kw(default='', when='defaults,entry', code='from stsci.tools import check_files; ans={ True:"yes",False:"no"}; OUT = ans[check_files.countInput(VAL) > 1]') In this case, ``_rule1_`` gets defined in the special parameter section ``[_RULES_]`` and triggered upon the editing of the parameter ``input``. The result of this logic will then automatically set the value of any section parameter with the ``is_set_by=_rule1_`` argument, such as the parameter ``driz_separate`` in the section ``[STEP 3: DRIZZLE SEPARATE IMAGES]`` The rule is executed within Python via two reserved words: ``VAL``, and ``OUT``. ``VAL`` is automatically set to the value of the parameter which was used to trigger the execution of the rule, right before the rule is executed. ``OUT`` will be the outcome of the rule code - the way it returns data to the rule execution machinery without calling a Python `return`. For the rule itself, one can optionally state (via the ``when`` argument) when the rule will be evaluated. The currently supported options for the ``when`` argument (used for rules only) are: * ``init``: Evaluate the rule upon starting the GUI * ``defaults``: Evaluate the rule when the parameter value changes because the user clicked the "Defaults" button * ``entry``: Evaluate the rule any time the value is changed in the GUI by the user manually * ``fopen``: Evaluate the rule any time a saved file is opened by the user, changing the value * ``always``: Evaluate the rule under any of these circumstances These options can be provided as a comma-separated list for combinations, although care should be taken to avoid any logic problems for when the rule gets evaluated. If a ``when`` argument is not supplied, the value of ``always`` is assumed. Tricky Rules ^^^^^^^^^^^^ A parameter can also be controlled by multiple other parameters using the same rule. The example below shows how to get ``par1`` to be grayed out if ``do_step1`` and ``do_step2`` are both disabled. In the .cfgspc file:: _task_name_ = string_kw(default="mytask") par1 = string_kw(default="", active_if="_rule1_", comment="Shared parameter") <other parameters removed...> [STEP 1: FOO] do_step1 = boolean_kw(default=True, triggers='_section_switch_', triggers='_rule1_', comment="Do Step 1?") <other parameters removed...> [STEP 2: BAR] do_step2 = boolean_kw(default=True, triggers='_section_switch_', triggers='_rule1_', comment="Do Step 2?") <more parameters removed, until we get to the end of the file...> [ _RULES_ ] _rule1_ = string_kw(default='', code='import mytask; OUT = mytask.tricky_rule(NAME, VAL)') In mytask.py file:: MY_FLAGS = {'do_step1': 'yes', 'do_step2': 'yes'} def tricky_rule(in_name, in_val): global MY_FLAGS MY_FLAGS[in_name] = in_val if MY_FLAGS['do_step1'] == 'yes' or MY_FLAGS['do_step2'] == 'yes': ans = True else: ans = False return ans For the rule itself, each rule has access to: * ``SCOPE`` * ``NAME`` - Parameter name. * ``VAL`` - Parameter value. * ``TEAL`` - Reference to the main TEAL object, which knows the value of all of its parameters. However, ``TEAL.getValue(NAME)`` returns its value *before* it is updated. To debug your tricky rule, you can add print-out lines to your rule. TEAL log under ``Help`` menu also shows you what it is doing. .. _`ConfigObj module`: https://configobj.readthedocs.io .. _`Numpy Documentation`: https://numpydoc.readthedocs.io/en/latest/format.html ����������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000033�00000000000�010211� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������27 mtime=1649147960.664998 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/����������������������������������������������������������������������������������0000755�0001750�0001750�00000000000�14223000071�012471� 5����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/GkiMpl.py�������������������������������������������������������������������������0000644�0001750�0001750�00000073105�14212324745�014253� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" matplotlib implementation of the gki kernel class """ import math import numpy import tkinter 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 . import gki from . import gkitkbase from . import textattrib from . import gkigcur from . 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 = tkinter.Frame(self.top) self.__pf.pack(side=tkinter.TOP, fill=tkinter.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=tkinter.TOP, fill=tkinter.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.draw() # or, could: self.gRedraw() with a .draw() # 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 is not 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 = arg[3:].astype(numpy.int8).tobytes().decode('ascii') 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 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 f"#{red:02x}{green:02x}{blue:02x}" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/MplCanvasAdapter.py���������������������������������������������������������������0000644�0001750�0001750�00000021565�14212324745�016260� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import matplotlib matplotlib.use('TkAgg') # set backend import matplotlib.backends.backend_tkagg as tkagg from .Ptkplot import hideTkCursor from .Ptkplot import FullWindowCursor import tkinter 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) # 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('<Expose>', self.tkExpose) # self.__theGwidget.bind('<Configure>', self.tkExpose) # init draw issue self.__theGwidget.bind('<Configure>', self.resize_widget, True) 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_widget(self, event): width, height = event.width, event.height # 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 # Need to deactivate cursor before resizing, then re-act. after self.wrappedRedrawOrResize(w=width, h=height) # also update the widget's w/h attrs; we will need this for the cursor self.__theGwidget.width = width self.__theGwidget.height = height # compute desired figure size in inches dpival = self.figure.dpi winch = width / dpival hinch = height / dpival self.figure.set_size_inches(winch, hinch, forward=False) self._tkcanvas.delete(self._tkphoto) self._tkphoto = tkinter.PhotoImage(master=self._tkcanvas, width=int(width), height=int(height)) self._tkcanvas.create_image(int(width / 2), int(height / 2), image=self._tkphoto) self.resize_event() def flush(self): self.__doIdleRedraw = 0 # draw could be defined to catch events before passing through # 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 is not 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("<Motion>", 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("<Motion>") 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() �������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647618327.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/Ptkplot.py������������������������������������������������������������������������0000644�0001750�0001750�00000024450�14215124427�014522� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os from tkinter import _default_root from tkinter import TclError, Canvas from . import wutil # 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 .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 import tkinter try: if _default_root: _default_root.destroy() except TclError: pass tkinter._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('<Expose>', self.tkExpose) self.bind('<Configure>', 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("<Motion>", self.moveCursor) if not self._SWCursor.isVisible(): self._SWCursor.draw() def deactivateSWCursor(self): self.update_idletasks() if self._isSWCursorActive: self._SWCursor.erase() self.unbind("<Motion>") 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 is not None: self.__window.delete(self.__tkHorLine) self.__tkHorLine = None if self.__tkVerLine is not 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 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/__init__.py�����������������������������������������������������������������������0000644�0001750�0001750�00000004301�14214070451�014611� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" PyRAF is a command language for running IRAF tasks in a Python like environment. It works very similar to IRAF, but has been updated to allow such things as importing Python modules, starting in any directory, GUI parameter editing and help. Most importantly, it can be imported into Python allowing you to run IRAF commands from within a larger script. """ import os import sys from .tools import irafglobals # noqa: F401 from .version import version as __version__ # noqa: F401 # Grab the terminal window's id at the earliest possible moment from . import wutil # noqa: F401 # Modify the standard import mechanism to make it more # convenient for the iraf module from . import irafimport # noqa: F401 # this gives more useful tracebacks for CL scripts from . import cllinecache # noqa: F401 from . import irafnames # noqa: F401 from . import irafexecute # noqa: F401 from . import clcache # 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 import atexit # noqa: E402 atexit.register(_cleanup) del atexit # Detect if the module was called via `python3 -m pyraf`: executable = sys.argv[0] while os.path.islink(executable): executable = os.readlink(executable) _pyrafMain = os.path.split(executable)[1] in ('pyraf', '-m', 'epyraf') del executable # now get ready to do the serious IRAF initialization # 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: from . import iraf if not _pyrafMain: iraf.Init(doprint=0, hush=1) except Exception: iraf = None raise help = iraf.help if '-m' not in sys.argv: from .__main__ import main # noqa: F401 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/__main__.py�����������������������������������������������������������������������0000644�0001750�0001750�00000013034�14214070451�014575� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /usr/bin/env python3 import sys import os import shutil import argparse from .tools import capable from .tools.irafglobals import yes, no, INDEF, EOF from . import iraf from .irafpar import makeIrafPar try: import IPython except ImportError: IPython = None def main(): if "." not in sys.path: sys.path.insert(0, ".") from . import __version__ # command-line options desc = ''' PyRAF is a command language for running IRAF tasks in a Python environment. ''' epilog = ''' For more info on PyRAF and IRAF see https://iraf-community.github.io ''' parser = argparse.ArgumentParser(prog='pyraf', description=desc, epilog=epilog) parser.add_argument('-c', '--command', help='Command passed in as string' ' (any valid PyRAF command)', metavar='cmd') parser.add_argument('-e', '--ecl', help='Turn on ECL mode', action='store_true', default=False) parser.add_argument('-m','--commandwrapper', help='Run command line wrapper to provide extra' ' capabilities (default)', action='store_true', dest='commandwrapper', default=True) parser.add_argument('-i','--no-commandwrapper', help='No command line wrapper, just run standard' ' interactive Python shell', action='store_false', dest='commandwrapper') parser.add_argument('-n', '--nosplash', help='No splash screen during startup (also see -x)', action='store_true', default=not capable.OF_GRAPHICS) parser.add_argument('-s', '--silent', help='Silent initialization (does not print' ' startup messages)', action='store_true', default=False) parser.add_argument('-V', '--version', help='Print version info and exit', action='version', version=__version__) parser.add_argument('-v', '--verbose', help='Set verbosity level (may be repeated' ' to increase verbosity)', action='count', default=0) parser.add_argument('-x', '--nographics', help='No graphics will be attempted/loaded' ' during session', action='store_true', default=False) if IPython is not None: parser.add_argument('-y', '--ipython', help='Run the IPython shell instead of the normal' ' PyRAF command shell', action='store_true', default=False) parser.add_argument('savefile', help='Optional savefile to start from', nargs='?') # allow the use of PYRAF_ARGS extraArgs = os.environ.get('PYRAF_ARGS', '').split() # Special case that the executable is called epyraf --> ECL mode if sys.argv[0] == 'epyraf': extraArgs.append('-e') args = parser.parse_args(sys.argv[1:] + extraArgs) # handle any warning supression right away, before any more imports if args.silent: import warnings warnings.simplefilter("ignore") # allow them to specifiy no graphics, done before any imports if args.nographics: os.environ['PYRAF_NO_DISPLAY'] = '1' # triggers on the rest of PyRAF capable.OF_GRAPHICS = False args.nosplash = True from . import pyrafglobals pyrafglobals._use_ecl = args.ecl from . import iraf iraf.setVerbose(args.verbose) # If not silent and graphics is available, use splash window if args.silent: splash_screen = None initkw = {'doprint': False, 'hush': True} else: initkw = {} if not args.nosplash: from . import splash splash_screen = splash.splash(f'PyRAF {__version__}') else: splash_screen = None if args.verbose > 0: print("pyraf: splashed") # read the user's startup file (if there is one) if 'PYTHONSTARTUP' in os.environ and \ os.path.isfile(os.environ["PYTHONSTARTUP"]): exec( compile( open(os.environ["PYTHONSTARTUP"]).read(), os.environ["PYTHONSTARTUP"], 'exec')) if args.savefile: iraf.Init(savefile=args.savefile, **initkw) else: iraf.Init(**initkw) if args.verbose > 0: print("pyraf: finished iraf.Init") if splash_screen is not None: splash_screen.Destroy() if not args.silent: print(f"PyRAF {__version__}") if args.command: iraf.task(cmd_line=args.command, IsCmdString=True) iraf.cmd_line() elif IPython is not None and args.ipython: if args.silent: IPython.embed(banner1='') else: IPython.embed() elif args.commandwrapper: from . import pycmdline cmdline = pycmdline.PyCmdLine(locals=globals()) if args.silent: cmdline.start('') # use no banner else: cmdline.start() # use default banner else: # run the standard Python interpreter import code if args.silent: code.interact(banner='', local=locals(), exitmsg='') else: code.interact(local=locals()) if __name__ == '__main__': main() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/aqutil.py�������������������������������������������������������������������������0000644�0001750�0001750�00000021666�14212324745�014374� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" 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. """ import os import struct import time import objc import AppKit # 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 windowID not 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^<argtype> # output par: o^<argtype> # inout par: N^<argtype> # return values are the first in the list, and 'v' is void # OSErr = objc._C_SHT CGErr = objc._C_INT INPSN = b'n^{ProcessSerialNumber=LL}' OUTPSN = b'o^{ProcessSerialNumber=LL}' # OUTPID = b'o^_C_ULNG' # invalid as of objc v3.2 WARPSIG = b'v{CGPoint=ff}' if struct.calcsize("l") > 4: # is 64-bit python WARPSIG = b'v{CGPoint=dd}' FUNCTIONS = [ # These are public API ('GetCurrentProcess', OSErr + OUTPSN), ('GetFrontProcess', OSErr + OUTPSN), # ( u'GetProcessPID', OSStat+INPSN+OUTPID), # see OUTPID note ('SetFrontProcess', OSErr + INPSN), ('CGWarpMouseCursorPosition', WARPSIG), ('CGMainDisplayID', objc._C_PTR + objc._C_VOID), ('CGDisplayPixelsHigh', objc._C_ULNG + objc._C_ULNG), ('CGDisplayHideCursor', CGErr + objc._C_ULNG), ('CGDisplayShowCursor', CGErr + objc._C_ULNG), # This is undocumented API ('CPSSetProcessName', OSErr + INPSN + objc._C_CHARPTR), ('CPSEnableForegroundOperation', OSErr + INPSN), ] bndl = AppKit.NSBundle.bundleWithPath_( objc.pathForFramework( '/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, b'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 ��������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/blankcursor.xbm�������������������������������������������������������������������0000644�0001750�0001750�00000000552�14203121554�015540� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#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 }; ������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/cgeneric.py�����������������������������������������������������������������������0000644�0001750�0001750�00000003713�14212324745�014645� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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. Created 1999 September 10 by R. White """ 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() �����������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/cl2py.py��������������������������������������������������������������������������0000644�0001750�0001750�00000247203�14214070451�014115� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""cl2py.py: Translate IRAF CL program to Python R. White, 1999 December 20 """ import io import os import sys from .generic import GenericASTTraversal from .clast import AST from .cltoken import Token from . import clscan from . import clparse from .clcache import codeCache from .tools.irafglobals import Verbose from .tools import basicpar, minmatch, irafutils from . import irafpar from . import 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=True): """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 _parser is None: _parser = clparse.getParser() if mode not in ["proc", "single"]: raise ValueError(f"Mode = `{mode}', must be `proc' or `single'") if filename not 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 with open(efilename, errors="ignore") as fh: clInput = fh.read() elif hasattr(filename, 'read'): clInput = filename.read() if usecache: index, pycode = codeCache.get(getattr(filename, 'name', None), 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 efilename = getattr(filename, 'name', '') 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("Code-string compiled by cl2py:", file=sys.stderr) print("-" * 80, file=sys.stderr) print(clInput, file=sys.stderr) print("-" * 80, file=sys.stderr) else: print("Code-file compiled by cl2py:" + efilename, file=sys.stderr) 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 " f".par file for task {vars.getProcName()}\n") 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), f"Warning: {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 = [f"Error in CL script {self.filename}"] except AttributeError: errmsg = ["Error in CL script"] for lineno, msg in self.errlist: if lineno: errmsg.append(f"{msg} (line {lineno:d})") 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 = [f"Warning in CL script {self.filename}"] except AttributeError: warnmsg = ["Warning in CL script"] for lineno, msg in self.warnlist: if lineno: warnmsg.append(f"{msg} (line {lineno:d})") 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 + "=" + repr(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(repr(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=" + repr(self.type), "name=" + repr(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] = repr(self.init_value) self.options["mode"] = "u" if self.shape is not None: arglist.append("array_size=" + repr(self.shape)) if self.list_flag: arglist.append("list_flag=" + repr(self.list_flag)) keylist = sorted(self.options.keys()) for key in keylist: option = self.options[key] if option is not None: arglist.append(key + "=" + repr(self.options[key])) if filename: arglist.append("filename=" + repr(filename)) if strict: arglist.append("strict=" + repr(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 + " = " + repr(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 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(f"Variable `{name}' is multiply declared", 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 = (f"{self.filename}: Variable `{self.current_var.name}' " "has more than one set of initial values") 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 as e: self.error( f"Bad initial value for variable `{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( f"Variable `{v.name}' has too many initial values", node) else: try: for i in range(len(v.init_value)): v.init_value[i] = _convFunc(v, v.init_value[i]) except ValueError as e: self.error( f"Bad initial value for array variable `{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 = (f"Unknown option `{optname}' " f"for variable `{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(f"Illegal procedure name `{proc_name}' starts with `.'", node) if pdot >= 0: self.warning(f"Bad procedure name `{proc_name}' " f"truncated after dot to `{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 = [f"Error in procedure `{self.getProcName()}'"] for v in self.local_vars_list: if v in self.proc_args_dict: errlist.append(f"Local variable `{v}' " "overrides parameter of same name") 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 = (f"Argument `{arg}' repeated " f"in procedure statement {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 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 = f"Procedure argument `{arg}' is not declared" 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 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: from . import iraf d = 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(f"GOTO refers to unknown label `{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 = sorted(self.goto_blockidlist.keys()) 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(f"Duplicate statement label `{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( f"GOTO branches to label `{label}' in inner block", node) def n_goto_stmt(self, node): label = str(node[1]) if label in self.label_blockid: self.error(f"Backwards GOTO to label `{label}' is not allowed", 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 or value == "INDEF": # (matches _INDEFClass object) 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 or value == "INDEF": # (matches _INDEFClass object) 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(f"Illegal value `{s}' " f"for boolean variable {var.name}") return s else: try: return value.bool() except AttributeError as e: raise AttributeError(var.name + ':' + str(e)) raise ValueError(f"unimplemented type `{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(f"Duplicate keyword `{keyword}' " f"in call to {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 " f"in call to {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 " f"in call to {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 = io.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(f"taskObj = iraf.getTask('{taskObj}')\n") # 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 = io.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 pyraf.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 as 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 = sorted(self.importDict.keys()) if keylist: 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 as e: raise AttributeError(self.filename + ':' + str(e)) if deflist: # add local and procedure parameters to Vars list if not noHdr: self.writeIndent("Vars = IrafParList(" + repr(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( f"_ecl = EclState(_ecl_linemap_{self.vars.proc_name})\n") # write goto label definitions if needed for label in self.gotos.labels(): self.writeIndent(f"class GoTo_{label}(Exception): pass") # 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(' + repr(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(f"Too few subscripts for array {s}", node) elif nsub > len(v.shape): self.error(f"Too many subscripts for array {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 = list(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(repr(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(repr(node), requireType, exprType) self.write(" ") else: self.write(repr(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(f"taskObj._ecl_safe_{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(" + repr(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(f"except GoTo_{irafutils.translateName(label)}:") self.incrIndent() self.writeIndent("pass") self.decrIndent() self.prune() def n_goto_stmt(self, node): self.write(f"raise GoTo_{irafutils.translateName(node[1].attr)}") self.prune() def n_inspect_stmt(self, node): 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.write(")") self.prune() def n_switch_stmt(self, node): self.inSwitch = self.inSwitch + 1 self.caseCount.append(0) self.write(f"SwitchVal{self.inSwitch:d} = ") 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(f"SwitchVal{self.inSwitch:d} in [") 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 = [] # 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 = io.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=<filename>, Stdin=<filename>, Stderr=<filename>, 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() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/clast.py��������������������������������������������������������������������������0000644�0001750�0001750�00000004273�14214070451�014170� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""clast.py: abstract syntax tree node type for CL parsing """ # 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 .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) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647416532.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/clcache.py������������������������������������������������������������������������0000644�0001750�0001750�00000015153�14214312324�014441� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""clcache.py: Implement cache for Python translations of CL tasks R. White, 2000 January 19 """ import os import sys import hashlib from .tools.irafglobals import Verbose, userIrafHome from . import filecache from . import pyrafglobals from . import sqliteshelve if 'PYRAF_CLCACHE_PATH' in os.environ: clcache_path = os.environ['PYRAF_CLCACHE_PATH'].split(':') if 'disable' in clcache_path: clcache_path = [] else: # create code cache userCacheDir = os.path.join(userIrafHome, 'pyraf') if not os.path.exists(userCacheDir): try: os.mkdir(userCacheDir) if '-s' not in sys.argv and '--silent' not in sys.argv: print(f'Created directory {userCacheDir} for cache') except OSError: print(f'Could not create directory {userCacheDir}') clcache_path = [userCacheDir, pyrafglobals.pyrafDir] # 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. def _currentVersion(): return "4e" if pyrafglobals._use_ecl else "4c" 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): self.writeCache = None self.cacheList = [] self.cacheFileList = [] write_flag = 'w' if cacheFileList: try: fname = cacheFileList[0] self.writeCache = sqliteshelve.open(fname, 'w') self.cacheList.append(self.writeCache) self.cacheFileList.append(fname) cacheFileList = cacheFileList[1:] except OSError as e: self.warning("Unable to open CL script cache " f"{fname} for writing") if self.writeCache is None: self.warning("Using in-memory cache as primary CL script cache") self.writeCache = dict() self.cacheList.append(self.writeCache) self.cacheFileList.append(':mem:') for fname in cacheFileList: try: db = sqliteshelve.open(fname, 'r') self.cacheList.append(db) self.cacheFileList.append(fname) except OSError as e: self.warning("Unable to open CL script cache " f"{fname} for reading", 1) self.clFileDict = _FileContentsCache() 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 close(self): """Close all cache files""" for cache in self.cacheList: try: cache.close() except AttributeError: pass self.cacheList.clear() self.writeCache = None # 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) + _currentVersion() elif source: # there is no filename, but return md5 digest of source as key h = hashlib.md5() h.update(source.encode()) return h.hexdigest() + _currentVersion() def add(self, index, pycode): """Add pycode to cache with key = index. Ignores if index=None.""" if index is not None and self.writeCache is not None: self.writeCache[index] = pycode 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 cache in self.cacheList: if index in cache: pycode = cache[index] pycode.index = index pycode.setFilename(filename) return index, pycode else: 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) if self.writeCache: if index in self.writeCache: del self.writeCache[index] self.warning(f"Removed {filename} from CL script cache " f"{self.cacheFileList[0]}", 2) else: self.warning(f"Did not find {filename} in CL script cache", 2) else: self.warning(f"Cannot remove {filename} from read-only " f"CL script cache {self.cacheFileList[0]}") codeCache = _CodeCache([os.path.join(d, 'clcache') for d in clcache_path]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/cllinecache.py��������������������������������������������������������������������0000644�0001750�0001750�00000006527�14214070451�015320� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""cllinecache.py: Modify linecache so it works with translated CL scripts too CL scripts have special filename "<CL script taskname>" """ import linecache import os from stat import ST_SIZE, ST_MTIME from .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 = list(cache.keys()) else: if filename in cache: filenames = [filename] else: return from . import iraf # used below for filename in filenames: # for filename in cache.keys(): if filename[:10] == "<CL script": entry = cache[filename] del cache[filename] # special CL script case - find original script file for time check if filename[10:13] == " CL": # temporary script created dynamically -- just save it save[filename] = entry else: size, mtime, lines, taskname = entry try: taskobj = iraf.getTask(taskname) fullname = taskobj.getFullpath() stat = os.stat(fullname) newsize = stat[ST_SIZE] newmtime = stat[ST_MTIME] except (os.error, IrafError): continue if size == newsize and mtime == newmtime: # save the ones that didn't change save[filename] = entry orig_checkcache() cache.update(save) def updatecache(filename, module_globals=None, orig_updatecache=linecache.updatecache): """Update a cache entry and return its list of lines. If something's wrong, discard the cache entry and return an empty list.""" if filename[:10] == "<CL script": # special CL script case return updateCLscript(filename) else: # original version handles other cases if module_globals is None: return orig_updatecache(filename) else: return orig_updatecache(filename, module_globals=module_globals) def updateCLscript(filename): cache = linecache.cache if filename in cache: del cache[filename] try: from . import iraf taskname = filename[11:-1] taskobj = iraf.getTask(taskname) fullname = taskobj.getFullpath() stat = os.stat(fullname) size = stat[ST_SIZE] mtime = stat[ST_MTIME] lines = taskobj.getCode().split('\n') cache[filename] = size, mtime, lines, taskname return lines except (IrafError, KeyError, AttributeError): return [] # insert these symbols into standard linecache module _original_checkcache = linecache.checkcache _original_updatecache = linecache.updatecache def install(): linecache.checkcache = checkcache linecache.updatecache = updatecache def uninstall(): linecache.checkcache = _original_checkcache linecache.updatecache = _original_updatecache install() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/clparse.py������������������������������������������������������������������������0000644�0001750�0001750�00000041150�14212324745�014514� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""clparse.py: Parse IRAF CL R. White, 1999 August 24 """ from .generic import GenericASTBuilder, GenericASTTraversal from .clast import AST from .cltoken import Token class CLStrictParser(GenericASTBuilder): """Strict version of CL parser (flags some program errors that CL accepts) This can be used as the parser to get a lint-like mode. Use CLParser (which adds some more rules to allow the same errors as the CL) to run CL programs. """ def __init__(self, AST, start='program'): GenericASTBuilder.__init__(self, AST, start) # list of tokens that should not be flattened by nonterminal() self.primaryTypes = { 'proc_stmt': 1, 'param_declaration_block': 1, 'declaration_stmt': 1, 'declaration_block': 1, 'var_name': 1, 'decl_init_list': 1, 'decl_init_value': 1, 'decl_array_dims': 1, 'array_subscript': 1, 'list_flag': 1, 'body_block': 1, 'statement_block': 1, 'nonnull_stmt': 1, 'osescape_stmt': 1, 'assignment_stmt': 1, 'task_call_stmt': 1, 'if_stmt': 1, 'for_stmt': 1, 'while_stmt': 1, 'break_stmt': 1, 'next_stmt': 1, 'return_stmt': 1, 'goto_stmt': 1, 'label_stmt': 1, 'switch_stmt': 1, 'case_block': 1, 'case_stmt_block': 1, 'case_value': 1, 'compound_stmt': 1, 'empty_compound_stmt': 1, 'task_arglist': 1, 'comma_arglist': 1, 'fn_arglist': 1, 'arg': 1, 'empty_arg': 1, 'non_empty_arg': 1, 'no_arg': 1, 'param_name': 1, 'opt_comma': 1, 'bool_expr': 1, } self._currentFname = None def parse(self, tokens, fname=None): """ Override this, only so we can add the optional fname arg. Delegate all parse logic to parent. """ self._currentFname = fname return GenericASTBuilder.parse(self, tokens) def typestring(self, token): try: return token.type except AttributeError: return token def error(self, token, value=None): finfo = '' if self._currentFname: finfo = 'file "' + self._currentFname + '"' if hasattr(token, 'lineno'): if len(finfo): finfo += ', ' errmsg = f"CL syntax error at `{token}' ({finfo}line {token.lineno:d})" else: if len(finfo): finfo = '(' + finfo + ')' errmsg = f"CL syntax error at `{token}' {finfo}" if value is not None: errmsg = errmsg + "\n" + str(value) raise SyntaxError(errmsg) def p_program(self, args): ''' program ::= proc_stmt param_declaration_block body_block program ::= statement_block proc_stmt ::= PROCEDURE IDENT proc_arguments end_of_line proc_arguments ::= ( proc_arglist ) proc_arguments ::= proc_arglist ::= IDENT proc_arglist ::= proc_arglist , IDENT proc_arglist ::= param_declaration_block ::= declaration_block declaration_block ::= declaration_list declaration_block ::= declaration_list ::= declaration_stmt end_of_line declaration_list ::= declaration_list declaration_stmt end_of_line declaration_stmt ::= TYPE decl_spec_list decl_spec_list ::= decl_spec decl_spec_list ::= decl_spec_list , decl_spec decl_spec ::= list_flag var_name opt_init_val declaration_options list_flag ::= * list_flag ::= decl_array_dims ::= INTEGER decl_array_dims ::= decl_array_dims , INTEGER var_name ::= IDENT var_name ::= IDENT [ decl_array_dims ] opt_init_val ::= = decl_init_list opt_init_val ::= decl_init_list ::= tdecl_init_list tdecl_init_list ::= decl_init_value tdecl_init_list ::= tdecl_init_list , decl_init_value decl_init_value ::= constant declaration_options ::= { decl_init_list , decl_options_list NEWLINE } declaration_options ::= { decl_options_list NEWLINE } declaration_options ::= { decl_init_list NEWLINE } declaration_options ::= decl_options_list ::= decl_option decl_options_list ::= decl_options_list , decl_option decl_option ::= IDENT = constant body_block ::= BEGIN end_of_line statement_block END end_of_line statement_block ::= statement_list statement_list ::= statement_list statement statement_list ::= statement ::= declaration_stmt end_of_line statement ::= nonnull_stmt end_of_line statement ::= end_of_line statement ::= label_stmt statement label_stmt ::= IDENT : end_of_line ::= NEWLINE end_of_line ::= ; nonnull_stmt ::= osescape_stmt nonnull_stmt ::= assignment_stmt nonnull_stmt ::= if_stmt nonnull_stmt ::= for_stmt nonnull_stmt ::= while_stmt nonnull_stmt ::= switch_stmt nonnull_stmt ::= break_stmt nonnull_stmt ::= next_stmt nonnull_stmt ::= return_stmt nonnull_stmt ::= goto_stmt nonnull_stmt ::= inspect_stmt nonnull_stmt ::= task_call_stmt nonnull_stmt ::= task_pipe_stmt nonnull_stmt ::= task_bkgd_stmt nonnull_stmt ::= { statement_list } opt_newline ::= NEWLINE opt_newline ::= opt_comma ::= , opt_comma ::= compound_stmt ::= opt_newline one_compound_stmt one_compound_stmt ::= empty_compound_stmt one_compound_stmt ::= nonnull_stmt empty_compound_stmt ::= ; osescape_stmt ::= OSESCAPE assignment_stmt ::= IDENT assignop expr assignment_stmt ::= array_ref assignop expr assignop ::= = assignop ::= ASSIGNOP if_stmt ::= IF ( bool_expr ) compound_stmt else_clause else_clause ::= opt_newline ELSE compound_stmt else_clause ::= while_stmt ::= WHILE ( bool_expr ) compound_stmt break_stmt ::= BREAK next_stmt ::= NEXT return_stmt ::= RETURN goto_stmt ::= GOTO IDENT inspect_stmt ::= = expr for_stmt ::= FOR ( opt_assign_stmt ; opt_bool ; opt_assign_stmt ) compound_stmt opt_assign_stmt ::= assignment_stmt opt_assign_stmt ::= opt_bool ::= bool_expr opt_bool ::= switch_stmt ::= SWITCH ( expr ) case_block case_block ::= opt_newline { case_stmt_list default_stmt_block NEWLINE } case_stmt_list ::= case_stmt_block case_stmt_list ::= case_stmt_list case_stmt_block case_stmt_block ::= opt_newline CASE case_value_list : compound_stmt case_value_list ::= case_value case_value_list ::= case_value_list , case_value case_value ::= INTEGER case_value ::= STRING case_value ::= QSTRING case_value ::= EOF default_stmt_block ::= opt_newline DEFAULT : compound_stmt default_stmt_block ::= task_call_stmt ::= IDENT task_arglist task_arglist ::= ( comma_arglist2 ) task_arglist ::= ( non_expr_arg ) task_arglist ::= ( no_arg ) task_arglist ::= comma_arglist no_arg ::= task_pipe_stmt ::= task_call_stmt PIPE task_call_stmt task_pipe_stmt ::= task_pipe_stmt PIPE task_call_stmt task_bkgd_stmt ::= task_call_stmt BKGD task_bkgd_stmt ::= task_pipe_stmt BKGD comma_arglist ::= ncomma_arglist comma_arglist ::= ncomma_arglist ::= non_empty_arg ncomma_arglist ::= empty_arg , arg ncomma_arglist ::= ncomma_arglist , arg comma_arglist2 ::= arg , arg comma_arglist2 ::= comma_arglist2 , arg non_empty_arg ::= expr non_empty_arg ::= non_expr_arg non_expr_arg ::= keyword_arg non_expr_arg ::= bool_arg non_expr_arg ::= redir_arg arg ::= non_empty_arg arg ::= empty_arg empty_arg ::= keyword_arg ::= param_name = expr bool_arg ::= param_name + bool_arg ::= param_name - param_name ::= IDENT redir_arg ::= REDIR expr bool_expr ::= expr expr ::= expr LOGOP not_expr expr ::= not_expr not_expr ::= ! comp_expr not_expr ::= comp_expr comp_expr ::= comp_expr COMPOP concat_expr comp_expr ::= concat_expr concat_expr ::= concat_expr // arith_expr concat_expr ::= arith_expr arith_expr ::= arith_expr + term arith_expr ::= arith_expr - term arith_expr ::= term term ::= term * factor term ::= term / factor term ::= term % factor term ::= factor factor ::= - factor factor ::= + factor factor ::= power power ::= power ** atom power ::= atom atom ::= number atom ::= IDENT atom ::= array_ref atom ::= STRING atom ::= QSTRING atom ::= EOF atom ::= BOOL atom ::= function_call atom ::= ( expr ) number ::= INTEGER number ::= FLOAT number ::= SEXAGESIMAL number ::= INDEF array_subscript ::= expr array_subscript ::= array_subscript , expr array_ref ::= IDENT [ array_subscript ] function_call ::= IDENT ( fn_arglist ) fn_arglist ::= comma_arglist constant ::= number constant ::= - number constant ::= + number constant ::= STRING constant ::= QSTRING constant ::= EOF constant ::= BOOL ''' pass def resolve(self, list): # resolve ambiguities # choose shortest; raise exception if two have same length rhs0 = list[0][1] rhs1 = list[1][1] assert len(rhs0) != len(rhs1) # print 'Ambiguity:' # for rule in list: # lhs, rhs = rule # print len(rhs), rule return list[0] def nonterminal(self, atype, args): # # Flatten AST a bit by not making nodes if there's only # one child, but retain a few primary structural # elements. # if len(args) == 1 and atype not in self.primaryTypes: return args[0] return GenericASTBuilder.nonterminal(self, atype, args) class CLParser(CLStrictParser): """Sloppy version of CL parser, with extra rules allowing some errors""" def __init__(self, AST, start='program'): CLStrictParser.__init__(self, AST, start) def p_additions(self, args): ''' program ::= statement_block END NEWLINE task_arglist ::= ( comma_arglist task_arglist ::= comma_arglist ) inspect_stmt ::= IDENT = ''' # - end without matching begin # - task argument list with missing closing parenthesis # - task argument list with missing opening parenthesis # - peculiar 'var =' form of inspect statement (as opposed to # normal '= var' form.) # # Note that the missing parentheses versions of # argument lists also permit parsing of 'pipe args' # in format taskname(arg, arg, | task2 arg, arg) pass class EclParser(CLParser): def __init__(self, AST, start='program'): CLParser.__init__(self, AST, start) self.primaryTypes['iferr_stmt'] = 1 def p_additions2(self, args): ''' nonnull_stmt ::= iferr_stmt iferr_stmt ::= if_kind guarded_stmt except_action iferr_stmt ::= if_kind guarded_stmt opt_newline THEN except_action iferr_stmt ::= if_kind guarded_stmt opt_newline THEN except_action opt_newline ELSE else_action if_kind ::= IFERR if_kind ::= IFNOERR guarded_stmt ::= { opt_newline statement_list } except_action ::= compound_stmt else_action ::= compound_stmt ''' pass # # list tree # class PrettyTree(GenericASTTraversal): def __init__(self, ast, terminal=1): GenericASTTraversal.__init__(self, ast) self.terminal = terminal self.indent = 0 self.nodeCount = 0 self.preorder() print() print(self.nodeCount, 'total nodes in tree') def n_NEWLINE(self, node): self.nodeCount = self.nodeCount + 1 def n_compound_stmt(self, node): self.indent = self.indent + 1 # self.printIndentNode(node) self.default(node) self.nodeCount = self.nodeCount + 1 def n_compound_stmt_exit(self, node): self.indent = self.indent - 1 self.printIndentNode(node, tail='_exit') # self.default(node,tail='_exit') def n_declaration_block(self, node): # dedent declaration blocks self.indent = self.indent - 1 self.default(node) self.nodeCount = self.nodeCount + 1 def n_declaration_block_exit(self, node): self.indent = self.indent + 1 self.default(node, tail='_exit') print() def n_BEGIN(self, node): self.printIndentNode(node) self.indent = self.indent + 1 self.nodeCount = self.nodeCount + 1 def n_END(self, node): self.indent = self.indent - 1 self.printIndentNode(node) self.nodeCount = self.nodeCount + 1 def n_nonnull_stmt(self, node): self.printIndentNode(node) self.nodeCount = self.nodeCount + 1 def n_declaration_stmt(self, node): self.printIndentNode(node) self.nodeCount = self.nodeCount + 1 def n_iferr_stmt(self, node): self.printIndentNode(node) self.nodeCount += 1 # print newline and indent def printIndent(self): print('\n', end=' ') for i in range(self.indent): print(' ', end=' ') # print newline, indent, and token def printIndentNode(self, node, tail=''): self.printIndent() self.default(node, tail=tail) def default(self, node, tail=''): if node.type == '}': self.printIndent() if isinstance(node, Token) or (not self.terminal): print(repr(node) + tail, end=' ') self.nodeCount = self.nodeCount + 1 class TreeList(GenericASTTraversal): def __init__(self, ast, terminal=0): GenericASTTraversal.__init__(self, ast) self.terminal = terminal self.indent = '' # self.postorder() self.preorder() def n_compound_stmt(self, node): self.indent = self.indent + '\t' def n_compound_stmt_exit(self, node): self.indent = self.indent[:-1] def default(self, node): if node.type == 'NEWLINE': print('\n' + self.indent, end=' ') elif isinstance(node, Token) or (not self.terminal): print(node, end=' ') def treelist(ast, terminal=1): PrettyTree(ast, terminal) def getParser(): from . import pyrafglobals if pyrafglobals._use_ecl: _parser = EclParser(AST) else: _parser = CLParser(AST) return _parser def parse(tokens, fname=None): global _parser return _parser.parse(tokens, fname=fname) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/clscan.py�������������������������������������������������������������������������0000644�0001750�0001750�00000102635�14214070451�014326� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""cl tokenizer/scanner using John Aycock's little languages (SPARK) framework This version uses a context-sensitive pattern stack R. White, 1999 September 10 """ from .cgeneric import ContextSensitiveScanner from .generic import GenericScanner from .cltoken import Token import re from .tools import irafutils from . import pyrafglobals # contexts for scanner _START_LINE_MODE = 0 # beginning of line _COMMAND_MODE = 1 # simple command mode _COMPUTE_START_MODE = 2 # initial compute mode (similar to command mode) _COMPUTE_EQN_MODE = 3 # compute mode in task arg when equation-mode # change flag has been seen. Reverts to # _COMPUTE_START_MODE on comma, redirection, etc. _COMPUTE_MODE = 4 # compute (script, equation) mode _SWALLOW_NEWLINE_MODE = 5 # mode at points where embedded newlines allowed _ACCEPT_REDIR_MODE = 6 # mode at points where redirection allowed # --------------------------------------------------------------------- # Regular Expressions for additional string replacement # --------------------------------------------------------------------- # # Match embedded comments in a multi-line string # Matches escaped newline followed by line with free-standing comment, # which we ignore to match unusual (ahem) IRAF behavior. comment_pat = re.compile(r'\\\s*\n\s*#.*\n\s*') # needed to prevent certain escapes to be protected to match IRAF # string behavior (only \\, \b, \n, \r, \t, \digits are converted into # special characters, all other's are left as is) special_escapes = re.compile(r'[\\\\]*(\\[^fnrt\\\'"\d])') def filterEscapes(instr): """Turn all backslashes that aren't special character for IRAF into double backslashes""" return special_escapes.sub(r'\\\1', instr) # --------------------------------------------------------------------- # Scanners for various contexts # --------------------------------------------------------------------- # --------------------------------------------------------------------- # BasicScanner: tokens recognized in all modes # --------------------------------------------------------------------- class _BasicScanner_1(GenericScanner): """Scanner class for tokens that can be recognized late""" def t_whitespace(self, s, m, parent): r'[ \t]+' pass def t_newline(self, s, m, parent): r'\n' parent.addToken(type='NEWLINE') parent.lineno = parent.lineno + 1 # reset mode at start of each line (unless newline was matched # as part of another pattern) parent.startLine() def t_rparen(self, s, m, parent): r'\)' parent.addToken(type=')') del parent.current[-1] parent.parencount = parent.parencount - 1 # add , as argument separator after this if parent.current and parent.current[-1] == _COMMAND_MODE: parent.argsep = ',' def t_pipe(self, s, m, parent): r'\|&?' # pipe is always recognized (it turns out) # this must be after the '||' pattern parent.addToken(type='PIPE', attr=s) # Pipe symbol puts us in start-line mode, but leaves # paren count (because pipes can occur inside task parentheses) parent.startLine(parencount=parent.parencount) parent.current.append(_SWALLOW_NEWLINE_MODE) def t_bkgd(self, s, m, parent): r'&' # background execution parent.addToken(type='BKGD', attr=s) def t_default(self, s, m, parent): r'.' parent.addToken(type=s) class _BasicScanner_2: """Scanner class for tokens that must be recognized before those defined in the _BasicScanner_1 class. """ def t_backslash(self, s, m, parent): r'\\[ \t]*\n' # trailing '\' completely absorbed # This allows spaces after \ and before newline -- I do not # allow that inside quotes. parent.lineno = parent.lineno + 1 def t_colon(self, s, m, parent): r':' parent.addToken(type=s) # add a newline after colon (which may appear in # label or case stmt) and go to start-line mode parent.addToken(type='NEWLINE') parent.startLine() class _BasicScanner_3: """Scanner class for Tokens that must be recognized before those defined in the _BasicScanner_2 or _BasicScanner_1 classes. """ def t_complex_redir(self, s, m, parent): r'> (>? ( [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<Comment>.*)' # 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_lbracket(self, s, m, parent): r'\[' parent.addToken(type=s) # push to compute mode parent.current.append(_COMPUTE_MODE) 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_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<KeyName>[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<KeyName>[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<KeyName>[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<KeyName>[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 from . 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(repr(tok), end=' ') if filename: sys.stdout.close() sys.stdout = sys.__stdout__ ���������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/cltoken.py������������������������������������������������������������������������0000644�0001750�0001750�00000015242�14214070451�014517� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""cltoken.py: Token definition for CL parser """ # 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 # from .tools.irafglobals import INDEF from .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 as 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:]) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/epar.optionDB���������������������������������������������������������������������0000644�0001750�0001750�00000000447�14203121554�015075� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������*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 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/epar.py���������������������������������������������������������������������������0000644�0001750�0001750�00000034326�14214070451�014013� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Main module for the PyRAF-version of the Epar parameter editor M.D. De La Pena, 2000 February 04 """ from .tools import capable if capable.OF_GRAPHICS: from tkinter.messagebox import askokcancel, showwarning, showerror import os import io from .tools import listdlg, eparoption, editpar, irafutils from . import iraf from . import irafpar from . import irafhelp from . import wutil from .pyrafglobals import pyrafDir else: wutil = None class editpar(): class EditParDialog(): pass # dummy so that code below can import from .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 OSError: 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": from . 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 is 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 = io.StringIO() iraf.system.help(taskname, page=0, Stdout=fh, Stderr=fh) result = fh.getvalue() fh.close() return result ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/filecache.py����������������������������������������������������������������������0000644�0001750�0001750�00000013216�14214070451�014762� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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. R. White, 2000 October 1 """ import os import stat import sys import hashlib 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.) """ raise NotImplementedError def updateValue(self): """Called when file has changed.""" raise NotImplementedError # 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 " f"of file {self.filename} " "is older than cached version") self.updateValue() self.attributes = newattr return self.getValue() def _getAttributes(self): """Get file attributes for a file or filehandle""" if not self.filename: return None st = os.stat(self.filename) # 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.""" with open(self.filename, mode="rb") as fh: contents = fh.read() # md5 digest is the value associated with the file h = hashlib.md5() h.update(contents) self.value = h.hexdigest() 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 abspath not 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() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/fill_clcache.py�������������������������������������������������������������������0000644�0001750�0001750�00000001154�14212324745�015453� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os import glob from . import cl2py, clcache def fill_clcache(): """Fill the system cache with precompiled CL code""" cl2py.codeCache = clcache._CodeCache([os.path.join( clcache.clcache_path[-1], 'clcache')]) n_compiled = 0 n_fail = 0 for cl_script in glob.glob(os.path.join(os.environ['iraf'], '**/*.cl'), recursive=True): try: cl2py.cl2py(cl_script) n_compiled += 1 except Exception: n_fail += 1 print(f"Compiled: {n_compiled}, Failed: {n_fail}") if __name__ == '__main__': fill_clcache() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/fontdata.py�����������������������������������������������������������������������0000644�0001750�0001750�00000034607�14212324745�014674� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""fontdata.py """ from numpy import int8, array 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)]) ] �������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/generic.py������������������������������������������������������������������������0000644�0001750�0001750�00000052655�14212324745�014513� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""generic.py: John Aycock's little languages (SPARK) framework """ # 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. __version__ = 'SPARK-0.6.1rlw' import re from . import 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 = f'(?P<{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): raise SyntaxError(f"Lexical error at position {pos}") 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): raise SyntaxError(f"Syntax error at or near `{token}' token") 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(cltoken.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 range(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() alist = [s[1] for s in sortlist] return children[name2index[self.resolve(alist)]] 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): def rebind(lhs, self=self): return ( 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_<node type>, falling # back onto the default() method if the n_* can't be found. The preorder # traversal also looks for an exit hook named n_<node type>_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): def rebind(func, self=self): return ( 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] �����������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/gki.py����������������������������������������������������������������������������0000644�0001750�0001750�00000143440�14214070451�013634� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" 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. """ import numpy import sys import re from .tools.irafglobals import IrafError from . import wutil from . import graphcap from . import irafgwcs from . import fontdata from .textattrib import (CHARPATH_RIGHT, JUSTIFIED_NORMAL, FONT_ROMAN, FQUALITY_NORMAL) from . import 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 range(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(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): return 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 is None: self.gkiPreferTtyIpc = 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 " f"{self.__class__.__name__}\n" "Special method name is incorrect: " + " ".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 is not 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): device = arg[2:].astype(numpy.int8).tobytes().decode().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 self.lastDevice is None 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: from . import gwm self.interactiveKernel = gwm.getGraphicsWindowManager() else: self.interactiveKernel = GkiNull() self.stdgraph = self.interactiveKernel else: from . 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 = iraf.envget("stdgraph", "") graphcap = getGraphcap() # protect against circular definitions devstr = device tried = {devstr: None} while devstr not in graphcap: pdevstr = devstr devstr = iraf.envget(pdevstr, "") if not devstr: raise IrafError("No entry found " f"for specified stdgraph device `{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" +' -> '.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(metacode.tobytes()) # 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 = iraf.osfn(iraf.envget('graphcap', 'dev$graphcap')) if filename not 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.) """ from . import gwm from . import gkiiraf if window is None: window = gwm.getActiveGraphicsWindow() if window is None: return gkibuff = window.gkibuffer.get() if len(gkibuff): graphcap = getGraphcap() stdplot = iraf.envget('stdplot', '') if not stdplot: msg = "No hardcopy device defined in stdplot" elif stdplot not in graphcap: msg = f"Unknown hardcopy device stdplot=`{stdplot}'" else: printer = gkiiraf.GkiIrafKernel(stdplot) printer.append(gkibuff) printer.flush() msg = "snap completed" stdout = kernel.getStdout(default=sys.stdout) stdout.write(f"{msg}\n") # ********************************************************************** 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(f"defaultColors should have {nIrafColors:d} " f"elements (has {len(self.defaultColors):d})") # 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(f"Bad cursor color ({color:d}) should be >=0 " f"and <{len(self.defaultColors)-1:d}") 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 range(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 range(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 range(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 range(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 from . import gwm if kernel: kernel.clearReturnData() kernel.flush() gwm.delete() kernel = None gwm._resetGraphicsWindowManager() kernel = GkiController() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/gkicmd.py�������������������������������������������������������������������������0000644�0001750�0001750�00000005042�14212324745�014321� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""gki metacode generating functions for use by Pyraf in generating iraf gki metacode (primarily for interactive graphics)""" from . import gki from . import 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.frombuffer(textstring.encode('ascii'), numpy.int8) 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 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/gkigcur.py������������������������������������������������������������������������0000644�0001750�0001750�00000022031�14214070451�014505� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" implement IRAF gcur functionality """ import sys import tkinter from .tools import irafutils from . 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 tkinter.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("<Button-1>", self.getMousePosition) self.gwidget.bind("<Key>", self.getKey) self.gwidget.bind("<Up>", self.moveUp) self.gwidget.bind("<Down>", self.moveDown) self.gwidget.bind("<Right>", self.moveRight) self.gwidget.bind("<Left>", self.moveLeft) self.gwidget.bind("<Shift-Up>", self.moveUpBig) self.gwidget.bind("<Shift-Down>", self.moveDownBig) self.gwidget.bind("<Shift-Right>", self.moveRightBig) self.gwidget.bind("<Shift-Left>", self.moveLeftBig) def unbind(self): self.gwidget.unbind("<Button-1>") self.gwidget.unbind("<Key>") self.gwidget.unbind("<Up>") self.gwidget.unbind("<Down>") self.gwidget.unbind("<Right>") self.gwidget.unbind("<Left>") self.gwidget.unbind("<Shift-Up>") self.gwidget.unbind("<Shift-Down>") self.gwidget.unbind("<Shift-Right>") self.gwidget.unbind("<Shift-Left>") 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): from . 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( f"Unimplemented CL gcur `:{colonString}'") else: self._setRetString(key, x, y, colonString) elif key == '=': # snap command - print the plot from . import gki gki.printPlot(self.window) elif key.isupper(): 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(f"{wx:g} {wy:g}") else: self.writeString(f"Unimplemented CL gcur command `{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 = f'\\{ord(key):03o}' self.retString = str(wx) + ' ' + str(wy) + ' ' + str(gwcs) + ' ' + key if colonString: self.retString = self.retString + ' ' + colonString self.top.quit() # time to go! �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/gkiiraf.py������������������������������������������������������������������������0000644�0001750�0001750�00000007354�14212324745�014507� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" OpenGL implementation of the gki kernel class """ import sys import os from . import gki from . import irafgwcs from . import iraftask from . import 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): from . import irafecl module = irafecl.getTaskModule() gki.GkiKernel.__init__(self) graphcap = gki.getGraphcap() if device not in graphcap: raise iraf.IrafError( f"No entry found for specified stdgraph device `{device}'") gentry = graphcap[device] self.device = device self.executable = executable = gentry['kf'] self.taskname = taskname = gentry['tn'] self.wcs = None if taskname not 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 = self.gkibuffer.delget().tobytes() # 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) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/gkitkbase.py����������������������������������������������������������������������0000644�0001750�0001750�00000103744�14214070451�015031� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Tk gui implementation for the gki plot widget """ import numpy import os import sys import time import tkinter from . import msgiobuffer from . import msgiowidget from . import wutil from .tools import capable, filedlg, irafutils from .tools.irafglobals import IrafError, userWorkingHome from . import gki from . import irafgwcs from .pyrafglobals import pyrafDir import tkinter.filedialog import tkinter.messagebox import tkinter.simpledialog 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<N>' where <N> 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 = tkinter.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 tkinter.TclError: try: self.top.option_readfile(os.path.join(userWorkingHome, optfile)) except tkinter.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=tkinter.TOP, expand=1, fill=tkinter.BOTH) self.gwidget.bind('<Enter>', 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 = tkinter.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 = tkinter.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=tkinter.BOTTOM, fill=tkinter.X) else: self.top.status = msgiowidget.MsgIOWidget(self.top, width=600) self.top.status.pack(side=tkinter.BOTTOM, fill=tkinter.X) # ----------------------------------------------- # Menu bar definitions def makeMenuBar(self): """Make menu bar at top of window""" self.menubar = tkinter.Frame(self.top, bd=1, relief=tkinter.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=tkinter.TOP, fill=tkinter.X) def makeFileMenu(self, menubar): button = tkinter.Menubutton(menubar, text='File') button.pack(side=tkinter.LEFT, padx=2) button.menu = tkinter.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 = tkinter.filedialog.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(self.gkibuffer.get().tobytes()) fh.close() def load(self, fname=None): """Load metacode from a file""" if not fname: if capable.OF_TKFD_IN_EPAR: fname = tkinter.filedialog.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.frombuffer(fh.read(), numpy.int16) 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 = tkinter.Menubutton(menubar, text='Edit') button.pack(side=tkinter.LEFT, padx=2) button.menu = tkinter.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=tkinter.NORMAL) self.editMenu.menu.entryconfigure(button.redrawOriginalNum, state=tkinter.NORMAL) else: self.editMenu.menu.entryconfigure(button.undoNum, state=tkinter.DISABLED) self.editMenu.menu.entryconfigure(button.redrawOriginalNum, state=tkinter.DISABLED) # disable Redo item if not redoable if buffer.isRedoable(): self.editMenu.menu.entryconfigure(button.redoNum, state=tkinter.NORMAL) else: self.editMenu.menu.entryconfigure(button.redoNum, state=tkinter.DISABLED) # disable Delete items if no plots if len(self.history) == 1 and self.isPageBlank(): self.editMenu.menu.entryconfigure(button.deleteNum, state=tkinter.DISABLED) self.editMenu.menu.entryconfigure(button.deleteAllNum, state=tkinter.DISABLED) else: self.editMenu.menu.entryconfigure(button.deleteNum, state=tkinter.NORMAL) self.editMenu.menu.entryconfigure(button.deleteAllNum, state=tkinter.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 tkinter.messagebox.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 = tkinter.Menubutton(menubar, text='Page') button.pack(side=tkinter.LEFT, padx=2) button.menu = tkinter.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=tkinter.NORMAL) else: menu.entryconfigure(button.nextNum, state=tkinter.DISABLED) # Back if page > 0: menu.entryconfigure(button.backNum, state=tkinter.NORMAL) else: menu.entryconfigure(button.backNum, state=tkinter.DISABLED) # First if page > 0: menu.entryconfigure(button.firstNum, state=tkinter.NORMAL) else: menu.entryconfigure(button.firstNum, state=tkinter.DISABLED) # Last if page < len(self.history) - 1: menu.entryconfigure(button.lastNum, state=tkinter.NORMAL) else: menu.entryconfigure(button.lastNum, state=tkinter.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 = f"<< {task}" elif i == pmax - 1 and pmax < lhis: label = f">> {task}" else: label = f"{i + 1:2d} {task}" menu.add_radiobutton(label=label, command=self.selectedPage, value=i, variable=self.bttnVar) # Make sure pageVar matches the real index value self.pageVar.set(self._currentPage) self.bttnVar.set(self._currentPage) def _setPageVar(self, *args): """Called when pageVar is changed (by .set() or by Page menu)""" n = self.pageVar.get() n = max(0, min(n, len(self.history) - 1)) if self._currentPage != n: self._currentPage = n self.gkibuffer, self.wcs, name, otherHistory = \ self.history[self._currentPage] self.setHistory(otherHistory) self.gRedraw() self.pageMenuInit() def backPage(self): self.prePageSelect() n = max(0, self._currentPage - 1) self.pageVar.set(n) self.bttnVar.set(n) def nextPage(self): self.prePageSelect() n = max(0, min(self._currentPage + 1, len(self.history) - 1)) self.pageVar.set(n) self.bttnVar.set(n) def firstPage(self): self.prePageSelect() self.pageVar.set(0) self.bttnVar.set(0) def lastPage(self): self.prePageSelect() self.pageVar.set(len(self.history) - 1) self.bttnVar.set(len(self.history) - 1) def selectedPage(self): self.prePageSelect() self.pageVar.set(self.bttnVar.get()) def refreshPage(self): self.prePageSelect() self.gRedraw() def prePageSelect(self): """ This is called right before a Page Menu selection is handled, usually causing a redraw. This is ONLY meant to be used for manual selections of the Page menu, no other task events. To be overridden. """ pass def makeWindowMenu(self, menubar): button = tkinter.Menubutton(menubar, text='Window') button.pack(side=tkinter.LEFT, padx=2) button.menu = tkinter.Menu(button, tearoff=0, postcommand=self.windowMenuInit) button.menu.add_command(label="New...", command=self.createNewWindow) # need to add separator here because menu.delete always # deletes at least one item button.menu.add_separator() button["menu"] = button.menu return button def windowMenuInit(self): menu = self.windowMenu.menu winVar = self.manager.getWindowVar() winList = sorted(self.manager.windowNames()) # Delete everything past the separator menu.delete('1', '10000') menu.add_separator() # Add radio buttons for windows for i in range(len(winList)): menu.add_radiobutton(label=winList[i], value=winList[i], variable=winVar) def createNewWindow(self): newname = tkinter.simpledialog.askstring( "New Graphics Window", "Name of new graphics window", initialvalue=self.manager.getNewWindowName()) if newname is not None: self.manager.window(newname) def makeHelpMenu(self, menubar): button = tkinter.Menubutton(menubar, text='Help') button.pack(side=tkinter.RIGHT, padx=2) button.menu = tkinter.Menu(button, tearoff=0) button.menu.add_command(label="Help...", command=self.getHelp) button["menu"] = button.menu return button def getHelp(self): """Display window with help on graphics""" hb = tkinter.Toplevel(self.top, visual='best') hb.title("PyRAF Graphics Help") hb.iconname("PyRAF Graphics Help") # Set up the Menu Bar with 'Close' button hb.menubar = tkinter.Frame(hb, relief=tkinter.RIDGE, borderwidth=0) hb.menubar.button = tkinter.Button(hb.menubar, text="Close", relief=tkinter.RAISED, command=hb.destroy) hb.menubar.button.pack() hb.menubar.pack(side=tkinter.BOTTOM, padx=5, pady=5) # Define the Listbox and setup the Scrollbar hb.list = tkinter.Listbox(hb, relief=tkinter.FLAT, height=25, width=80, selectmode=tkinter.SINGLE, selectborderwidth=0) scroll = tkinter.Scrollbar(hb, command=hb.list.yview) hb.list.configure(yscrollcommand=scroll.set) hb.list.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=1) scroll.pack(side=tkinter.RIGHT, fill=tkinter.Y) # Insert each line of the helpString into the box listing = helpString.split('\n') for line in listing: hb.list.insert(tkinter.END, line) # ----------------------------------------------- # start general functionality (independent of graphics panel # implementation) def activate(self): """Make this the active window""" self.gwidget.activate() def errorMessage(self, text): """Truncate number of error messages produced in a plot.""" if self._errorMessageCount < self.MAX_ERROR_COUNT: print(text) self._errorMessageCount = self._errorMessageCount + 1 elif self._errorMessageCount == self.MAX_ERROR_COUNT: print("\nAdditional graphics error messages suppressed") self._errorMessageCount = self._errorMessageCount + 1 def flush(self): """Flush any pending graphics requests""" try: if self.gwidget: self.gwidget.update_idletasks() except tkinter.TclError: pass def hasFocus(self): """Returns true if this window currently has focus""" return wutil.getTopID(wutil.getFocalWindowID()) == \ wutil.getTopID(self.getWindowID()) def setDrawingColor(self, irafColorIndex): self.colorManager.setDrawingColor(irafColorIndex) def setCursorColor(self, irafColorIndex): self.colorManager.setCursorColor(irafColorIndex) def getWindowName(self): return self.windowName def gwdestroy(self): """Delete this object from the manager window list""" # if gcur is active, terminate it self.gcurTerminate() self.gwidget = None self.top.after_idle(self.manager.delete, self.windowName) def forceNextDraw(self): """ This is a hook meant to be overridden. In general, if this is called, it means that the caller knows that the very next draw should surely be done (not skipped, e.g. for performance sake). This will only be useful to subclasses which are sparing with graphics draws. """ pass # ----------------------------------------------- # the following methods implement the FocusEntity interface # used by wutil.FocusController def saveCursorPos(self): """save current position if window has focus and cursor is in window, otherwise do nothing""" if not self.hasFocus(): # window does not have focus return gwidget = self.gwidget if gwidget: x = gwidget.winfo_pointerx() - gwidget.winfo_rootx() y = gwidget.winfo_pointery() - gwidget.winfo_rooty() maxX = gwidget.winfo_width() maxY = gwidget.winfo_height() if x < 0 or y < 0 or x >= 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() != tkinter.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() ����������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/gkitkplot.py����������������������������������������������������������������������0000644�0001750�0001750�00000034367�14212324745�015107� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Tkplot implementation of the gki kernel class """ import numpy import tkinter from . import wutil from . import Ptkplot from . import gki from . import gkitkbase from . import gkigcur from . import tkplottext 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(tkinter.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 = arg[3:].astype(numpy.int8).tobytes().decode('ascii') 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(tkinter.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 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 range(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 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 tkinter""" color = self.config.defaultColors[irafColorIndex] red = int(255 * color[0]) green = int(255 * color[1]) blue = int(255 * color[2]) return f"#{red:02x}{green:02x}{blue:02x}" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/graphcap.py�����������������������������������������������������������������������0000644�0001750�0001750�00000010032�14214070451�014635� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Finds device attributes from the graphcap """ from .tools import compmixin from . 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(f"Graphcap entry does not have any colons\n{entry}") return entry[:aend].split("|")[:-1] def getAttributes(entry): abeg = entry.find(':') if abeg < 0: raise ValueError(f"Graphcap entry does not have any colons\n{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""" with open(self.filename, errors="ignore") as fh: lines = fh.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 key not 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 True: 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) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/gwm.py����������������������������������������������������������������������������0000644�0001750�0001750�00000017362�14214070451�013657� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Graphics window manager, creates multiple toplevel togl widgets for use by python plotting """ import os from .tools import capable if capable.OF_GRAPHICS: import tkinter from . import wutil from . import 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 True: windowName = root + str(number) if windowName not 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 windowName not 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 = tkinter.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 list(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(f"error: graphics window `{windowName}' doesn't exist") 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 = list(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": from . import gkitkplot kernel = gkitkplot.GkiTkplotKernel elif kernelname == "opengl": print("OpenGL kernel no longer exists, using default instead") kernelname = "default" elif kernelname == "matplotlib": try: from . 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": from . 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() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/ipython_api.py��������������������������������������������������������������������0000644�0001750�0001750�00000037505�14214070451�015411� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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 """ # ***************************************************************************** # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. # ***************************************************************************** from IPython.core import release from IPython.terminal.interactiveshell import TerminalInteractiveShell __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 . import iraf, __version__ from .irafpar import makeIrafPar from .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 .irafcompleter import IrafCompleter class IPythonIrafCompleter(IrafCompleter): 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 >>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 >>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 = TerminalInteractiveShell.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 TerminalInteractiveShell.init_readline = pyraf_init_readline # Override class method def uninstall_init_readline_hack(self): TerminalInteractiveShell.init_readline = self._ipython_init_readline # restore class method del self._ipython_init_readline # --------------------------------------------------------------------------- class IPython_PyRAF_Integrator: """This class supports the integration of these features with IPython: 1. PyRAF readline completion 2. PyRAF CL translation 3. PyRAF exception traceback simplification """ def __init__(self, clemulate=1, cmddict={}, cmdchars=("a-zA-Z_.", "0-9")): import re import os self.reword = re.compile('[a-z]*') self._cl_emulation = clemulate self.cmddict = cmddict self.recmd = re.compile("[ \t]*(?P<cmd>" + "[" + 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" 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 = list( 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) asciiline = str(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 import 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 not (line[i].isalnum() and line[i].isletter()) 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 import traceback import os import IPython.ultraTB # get the color scheme from the user configuration file and pass # it to the trace formatter csm = 'Linux' # default 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 == "<ipython console>": 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 TerminalInteractiveShell._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 ValueError: import sys print("usage:", usage, "[on | off]", file=sys.stderr) 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(a, end=' ', file=sys.stderr) print(file=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 <magic_function>"): 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 '-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 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/ipythonrc-pyraf�������������������������������������������������������������������0000644�0001750�0001750�00000001710�14203121554�015561� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- Mode: Shell-Script -*- Not really, but shows comments correctly #*************************************************************************** # # Configuration file for ipython -- ipythonrc format # # The format of this file is one of 'key value' lines. # Lines containing only whitespace at the beginning and then a # are ignored # as comments. But comments can NOT be put on lines with data. #*************************************************************************** # This is an example of a 'profile' file which includes a base file and adds # some customizaton for a particular purpose. # If this file is found in the user's ~/.ipython directory as # ipythonrc-pyraf, it can be loaded by calling passing the '-profile # pyraf' (or '-p pyraf') option to IPython. # A simple alias pyraf -> 'ipython -p pyraf' makes life very convenient. # Load the user's basic configuration include ipythonrc # import ... import_mod pyraf.ipython_api import_mod iraf ��������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/iraf.py���������������������������������������������������������������������������0000644�0001750�0001750�00000000620�14212324745�014001� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""module iraf.py -- home for all the IRAF tasks and basic access functions R. White, 1999 Jan 25 """ from .iraffunctions import * # a few CL tasks have modified names (because they start with '_') from . import iraffunctions _curpack = iraffunctions.curpack _allocate = iraffunctions.clAllocate _deallocate = iraffunctions.clDeallocate _devstatus = iraffunctions.clDevstatus del iraffunctions ����������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647588437.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafcompleter.py������������������������������������������������������������������0000644�0001750�0001750�00000033127�14215032125�015713� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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. RLW, 2000 February 13 """ import builtins import __main__ import re import keyword import glob import os from . import iraf from .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 = f"Control-{ichar}" char2lab[char] = lab lab2char[lab] = char lab2char[f"Control-{ichar}"] = char lab2char[fr"\C-{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 complete(self, text, state): """Return the next possible completion for 'text'.""" # Overwrite weird behaviour in rlcompleter.Completer with # empty input if not text.strip(): if state == 0: self.matches = self.global_matches('') try: return self.matches[state] except IndexError: return None return Completer.complete(self, text, state) def activate(self, char="\t"): """Turn on completion using the specified character""" if readline is 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(f"{lab}: complete") 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 OSError as 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 is not None and self.completionChar: # restore normal behavior for previous completion character lab = char2lab.get(self.completionChar, self.completionChar) readline.parse_and_bind(f"{lab}: self-insert") 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 is 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, builtins.__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<TAB> 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(fr'[\w.~$+-{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(f"{head}.getAllMatches({repr(tail)})") def addhead(s, head=head + "."): return head + s return list(map(addhead, matches)) def activate(c="\t"): completer.activate(c) def deactivate(): completer.deactivate() �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafdisplay.py��������������������������������������������������������������������0000644�0001750�0001750�00000023721�14214070451�015370� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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. """ import os import numpy import socket import sys import fcntl from .tools import irafutils _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 <domain>:<address> where <domain> 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 OSError: pass raise OSError("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(f"Illegal image device specification `{imtdev}'") class ImageDisplay: """Interface to IRAF-compatible image display""" # constants for cursor read _IIS_READ = 0o100000 _IMC_SAMPLE = 0o040000 _IMCURSOR = 0o20 _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(a.tobytes()) 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: raise OSError("Error reading from image display") def _write(self, s): """Write string s to image display Raises IOError on failure """ n = len(s) while n > 0: nwritten = os.write(self._fdout, s[-n:]) n -= nwritten if nwritten <= 0: raise OSError("Error writing to image display") class FifoImageDisplay(ImageDisplay): """FIFO version of image display""" def __init__(self, infile, outfile): ImageDisplay.__init__(self) 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) 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 is 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 OSError: raise OSError("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 OSError: 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 �����������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafecl.py������������������������������������������������������������������������0000644�0001750�0001750�00000037214�14214070451�014470� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""This module adds IRAF ECL style error handling to PyRAF.""" import inspect import sys from .tools.irafglobals import Verbose from . import pyrafglobals from . import iraftask from . import irafexecute from . import iraf executionMonitor = None class EclState: """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: from . import irafecl return irafecl else: return iraftask class Erract: """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 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 = iraf.redirProcess(kw) # set parameters kw['_setMode'] = 1 self.setParList(*args, **kw) if Verbose > 1: print(f"run {self._name} ({self.__class__.__name__}: " f"{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('Successful task termination', file=sys.stderr) 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 as 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, 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: 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 = (f"Error ({errno:d}): on line {self._ecl_get_lineno():d} " f"of '{self._name}' from '{errtask}':\n\t'{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(f"Warning on line {self._ecl_get_lineno():d} " f"of '{self._name}': " "divide by zero - using $err_dzvalue =", self.DOLLARerr_dzvalue) return self.DOLLARerr_dzvalue else: iraf.error(1, "divide by zero", self._name, suppress=False) if isinstance(a, int) and isinstance(b, int): return a // b else: 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(f"Warning on line {self._ecl_get_lineno():d} " f"of task '{self._name}': " "modulo by zero - using $err_dzvalue =", self.DOLLARerr_dzvalue) return self.DOLLARerr_dzvalue else: 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(f"ERROR ({e.errno:d}): {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: with open(cl_file, errors="ignore") as fh: cl_code = fh.readlines()[lineno - 1].strip() except OSError: cl_code = "<source code not available>" 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(f" line {lineno:d}: {cl_file}") parent = _ecl_parent_task() if parent: parent_lineno = self._ecl_get_lineno() parent_file = parent.getFilename() try: with open(parent_file, errors="ignore") as fh: parent_code = fh.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) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafexecute.py��������������������������������������������������������������������0000644�0001750�0001750�00000116243�14214070451�015367� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""irafexecute.py: Functions to execute IRAF connected subprocesses """ import os import re import signal import struct import sys import numpy import io from .tools import irafutils from .tools.irafglobals import IrafError, IrafTask, Verbose from . import subproc from . import filecache from . import wutil from . import gki from . import irafukey from . import irafgwcs from . import iraf # stdgraph = None IPC_PREFIX = b'P\x02' # 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): if os.path.exists(arg): return arg task = iraf.getTask(arg, found=1) if task is not None: return task.getFullpath() raise IrafProcessError(f"Cannot find task or executable {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(f"Warning: process {executable} is bad, " "restarting it\n", 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 = sorted(self._data.values()) 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 = iraf.getTask(taskname, found=1) if task is None: print(f"No such task `{taskname}'") elif task.__class__.__name__ == "IrafTask": # cache only executable tasks (not CL tasks, etc.) executable = task.getFullpath() process = self.get(task, iraf.getVarDict()) self.add(process) if executable in self._data: self._locked[executable] = 1 else: self.error(f"Cannot cache {taskname}\n") 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 = iraf.getTask(taskname, found=1) if task is not None: self.terminate(task) else: for rank, proxy in list(self._data.values()): executable = proxy.process.executable if executable not in self._locked: self.terminate(executable) def list(self): """List processes sorted from newest to oldest with locked flag""" values = sorted(self._data.values()) values.reverse() n = 0 for rank, proxy in values: n = n + 1 executable = proxy.process.executable if executable in self._locked: print(f"{n:2d}: L {executable}") else: print(f"{n:2d}: {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) as value: raise # 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) as exc: # on error, kill the subprocess, then re-raise the original exception try: processCache.kill(irafprocess) except Exception as 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<gname>[a-zA-Z_$][\w.]*(?:\[\d+\])?)\s*\n' _p_par_set = r'(?P<sname>[a-zA-Z_][\w.]*(?:\[\d+\])?)\s*=\s*(?P<svalue>.*)\n' _re_msg = re.compile(r'(?P<par_get>' + _p_par_get + ')|' + r'(?P<par_set>' + _p_par_set + ')') _p_curpack = r'_curpack(?:\s.*|)\n' _p_stty = r'stty.*\n' _p_sysescape = r'!(?P<sys_cmd>.*)\n' _re_clcmd = re.compile(r'(?P<curpack>' + _p_curpack + ')|' + r'(?P<stty>' + _p_stty + ')|' + r'(?P<sysescape>' + _p_sysescape + ')') class IrafProcess: """IRAF process class""" def __init__(self, executable): """Start IRAF task 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(f"set {key}={str(value)}\n") outenvstr.append(f"chdir {os.getcwd()}\n") 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 """ 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(f'set ttynlines={nlines:d}\n' f'set ttyncols={ncols:d}\n') 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): pass # No more Mr. Nice Guy try: self.process.die() except subproc.SubprocessError as e: if Verbose > 0: # too bad, if we can't kill it assume it is already dead self.stderr.write(f"Warning: cannot terminate process {e}\n") 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() from . import pyrafglobals if verbose and not pyrafglobals._use_ecl: sys.stderr.write(f"Killing IRAF task `{self.task.getName()}'\n") 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 string or bytes to IRAF form and write to IRAF process""" if isinstance(s, str): s = s.encode() self.write(Bytes2Iraf(s)) def readString(self): """Read IRAF string from process and convert to ascii string""" return Iraf2Bytes(self.read()).decode() def write(self, data): """write binary data to IRAF process in blocks of <= 4096 bytes""" i = 0 block = 4096 try: while i < len(data): # Write: # IRAF magic number # number of following bytes # data dsection = data[i:i + block] # the arg parts to the following are all type bytes in PY3K self.process.write(IPC_PREFIX + struct.pack('=h', len(dsection)) + dsection) i = i + block except subproc.SubprocessError as e: raise IrafProcessError(f"Error in write: {str(e)}") def read(self): """Read binary data from IRAF pipe""" try: # read pipe header first (self.process is subproc.Subprocess) header = self.process.read(4) # read returns bytes if header[0:2] != IPC_PREFIX: raise IrafProcessError("Not a legal IRAF pipe record: " + str(header[0:2])) ntemp = struct.unpack('=h', header[2:]) nbytes = ntemp[0] # read the rest data = self.process.read(nbytes) # read returns bytes return data except subproc.SubprocessError as e: raise IrafProcessError(f"Error in read: {str(e)}") def slave(self): """Talk to the IRAF process in slave mode. Raises an IrafProcessError if an error occurs.""" self.msg = '' self.xferline = b'' # try to speed up loop a bit re_match = _re_msg.match xfer = self.xfer xmit = self.xmit par_get = self.par_get par_set = self.par_set executeClCommand = self.executeClCommand while True: # each read may return multiple lines; only # read new data when old has been used up if not self.msg: self.msg = self.readString() msg = self.msg msg5 = msg[:5] if msg5 == 'xfer(': xfer() elif msg5 == 'xmit(': xmit() elif msg[:4] == 'bye\n': return elif msg5 in ['error', 'ERROR']: errno, text = self._scanErrno(msg) raise IrafProcessError("IRAF task terminated abnormally\n" + msg, errno=errno, errmsg=text, errtask=self.task.getName()) else: # pattern match to see what this message is mcmd = re_match(msg) # assume each par_set msg ends with either a 'bye' line or with a par_get line if (mcmd and mcmd.group('par_set')) or len( self.par_set_msg_buf) > 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( f"Program bug: uninterpreted message `{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): # 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 as e: # on ValueError, just print warning and then force set if Verbose > 0: self.stderr.write(f'Warning: {e}\n') 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" f"(got {len(xdata):d}, " f"expected {2 * nbytes:d}, chan {chan:d})") 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 = Iraf2Bytes(xdata) if checkForEscapeSeq: if (txdata[0:5] == b"\033+rAw"): # Turn on RAW mode for STDIN self.stdinIsraw = True return if (txdata[0:5] == b"\033-rAw"): # Turn off RAW mode for STDIN self.stdinIsraw = False return if (txdata[0:5] == b"\033=rDw"): # ignore IRAF io escape sequences for now # This mode enables screen redraw code return if hasattr(self.stdout, "buffer"): self.stdout.buffer.write(txdata) else: self.stdout.write(txdata.decode()) self.stdout.flush() elif chan == 5: sys.stdout.flush() if hasattr(self.stderr, "buffer"): self.stderr.buffer.write(Iraf2Bytes(xdata)) else: self.stderr.write(Iraf2Bytes(xdata).decode()) self.stderr.flush() elif chan == 6: gki.kernel.append(numpy.frombuffer(xdata, dtype=numpy.int16)) elif chan == 7: stdimagekernel.append(numpy.frombuffer(xdata, dtype=numpy.int16)) elif chan == 8: self.stdout.write("data for STDPLOT\n") self.stdout.flush() elif chan == 9: sdata = numpy.frombuffer(xdata, dtype=numpy.int16) 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 " f"for channel {forChan:d}\n") self.stdout.flush() else: self.stdout.write(f"data for channel {chan:d}\n") 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. if hasattr(self.stdin, 'buffer'): line = self.stdin.buffer.read(nchars) else: 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(f"xfer request for unknown channel {chan:d}") 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(f"Illegal message format `{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) 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:] != f"> {IPCOUT}\n": raise IrafProcessError( f"Error: cannot understand IPCOUT syntax in `{cmd}'") sys.stdout.flush() # strip the redirection off and capture output of command buffer = io.StringIO() # redirect other I/O (but don't use graphics status line) 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(f'set ttynlines={nlines:d}\n' f'set ttyncols={ncols:d}\n') self.msg = self.msg[mcmd.end():] elif mcmd.group('curpack'): # current package request self.writeString(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 = 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 " f"`{self.msg}'") # IRAF string conversions using numpy module def Bytes2Iraf(b): """translate bytes to IRAF 16-bit string format""" inarr = numpy.frombuffer(b, numpy.int8) retval = inarr.astype(numpy.int16).tobytes() # log_task_comm('Asc2IrafString (write to task)', retval, False) return retval def Iraf2Bytes(iraf_string): """translate 16-bit IRAF characters to ascii""" inarr = numpy.frombuffer(iraf_string, numpy.int16) retval = inarr.astype(numpy.int8).tobytes() # 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, str, bytes)), "?!: " + str(type(strbuf)) if expectAsStr: assert isinstance(strbuf, str), "Unexpected type: " + str(type(strbuf)) if isinstance(strbuf, (str, str)): 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) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1648793030.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/iraffunctions.py������������������������������������������������������������������0000644�0001750�0001750�00000354054�14221512706�015743� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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. R. White, 2000 January 20 """ # define INDEF, yes, no, EOF, Verbose, IrafError, userIrafHome from .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): 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 # ----------------------------------------------------- # hide these modules so we can use 'from iraffunctions import *' import sys as _sys import os as _os import string as _string import re as _re import math as _math import struct as _struct import types as _types import time as _time import fnmatch as _fnmatch import glob as _glob import tempfile as _tempfile import linecache as _linecache import pickle as _pickle import io as _io from .tools import minmatch as _minmatch from .tools import irafutils as _irafutils from .tools import teal as _teal import numpy as _numpy from decimal import Decimal as _Decimal, ROUND_HALF_UP as _ROUND_HALF_UP from . import subproc as _subproc from . import wutil as _wutil from . import irafnames as _irafnames from . import irafinst as _irafinst from . import irafpar as _irafpar from . import iraftask as _iraftask from . import irafexecute as _irafexecute from . import cl2py as _cl2py from . import gki from . import irafecl try: from . import sscanf except OSError: # basic usage does not actually require sscanf sscanf = None print("Warning: sscanf library not installed on " + sys.platform) # 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'] 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 key not in _os.environ: _os.environ[key] = value iraf = _os.environ['iraf'] except OSError: raise OSError(""" Your "iraf" environment variable is not defined and could not be determined from /usr/local/bin/cl. This is are needed to find IRAF tasks. Before starting pyraf, define ot by doing (for example): export iraf=/iraf/iraf/ at the Unix command line. Actual values will depend on your IRAF installation, and they are set during the IRAF user installation (see https://iraf-community.github.io). Also be sure to run the "mkiraf" command to create a logion.cl (http://www.google.com/search?q=mkiraf). """) arch = _os.environ.get('IRAFARCH', '') # stacksize problem on linux # https://iraf-community.github.io/iraf-v216/issues/61 if arch in ('redhat', 'linux', 'linuxppc', 'suse', 'macosx'): import resource try: hardlimit = resource.getrlimit(resource.RLIMIT_STACK)[1] resource.setrlimit(resource.RLIMIT_STACK, (hardlimit, hardlimit)) except (ValueError, OSError): pass # Ignore the error and hope the best... # 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 if _irafinst.EXISTS: fname = 'hlib$clpackage.cl' else: fname = f'{_irafinst.NO_IRAF_PFX}/clpackage.cl' global clpkg clpkg = IrafTaskFactory('', 'clpackage', '.pkg', fname, '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 not _irafinst.EXISTS: fname = f'{_irafinst.NO_IRAF_PFX}/login.cl' elif 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 access('/etc/iraf/login.cl'): fname = '/etc/iraf/login.cl' 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 OSError(f"CL startup file {file} does not exist") with open(file, errors="ignore") as fh: lines = fh.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(f'echo "{var}=${var}"\n') newlines.append('exit 0\n') else: newlines.append(line) if nfound == 0: raise OSError(f"No exec statement found in script {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, 0o700) # run new script and capture output fh = _io.StringIO() status = clOscmd(newfile, Stdout=fh) if status: raise OSError(f"Execution error in script {newfile} (derived from {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 = [ '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 OSError(f"Output file `{savefile}' already exists") # 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 list(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 .tools import irafglobals import __main__ import pyraf from . import irafpar from . import 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: ' + repr(list(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 '_doprint' not in kw: kw['_doprint'] = doprint if '_hush' not in kw: kw['_hush'] = hush if '_save' not 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 '_save' not in kw: kw['_save'] = save # if '_parent' not 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): 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, <pkg>.<taskname> 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 range(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( f"Task `{taskname}' is ambiguous, " f"could be {', '.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 range(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(f"Bad package name `{repr(pkgname)}'") # undo any modifications to the pkgname pkgname = _irafutils.untranslateName(pkgname) return _pkgs[pkgname] except _minmatch.AmbiguousKeyError as e: # re-raise the error with a bit more info raise e.__class__("Package " + pkgname + ": " + str(e)) except KeyError: if found: return None raise KeyError(f"Package `{pkgname}' not found") # ----------------------------------------------------- # 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 list(_tasks.keys()) def getTaskObjects(): """Returns list of all defined IrafTask objects""" return list(_tasks.values()) def getPkgList(): """Returns list of names of all defined IRAF packages""" return list(_pkgs.keys()) def getLoadedList(): """Returns list of names of all loaded IRAF packages""" return list(_loaded.keys()) def getVarDict(): """Returns dictionary all IRAF variables""" return _varDict def getVarList(): """Returns list of names of all IRAF variables""" return list(_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 range(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 range(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, _iraftask.IrafPkg)): pkglist = [pkglist] for p in pkglist: try: pthis = getPkg(p) if pthis.isLoaded(): pkgdict[pthis.getName()] = 1 else: _writeError(f'Package {pthis.getName()}' ' has not been loaded') except KeyError as 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 range(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(f"{prefix}{word}{equals}{envget(word)}") @handleRedirAndSaveKwds def gripes(): """ Hide the system call """ print("No gripes") 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 as 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(f"Undefined environment variable `{var}'") _tmpfileCounter = 0 def mktemp(root): """Make a temporary filename starting with root""" global _tmpfileCounter basename = root + repr(_os.getpid()) while True: _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 = "_" + repr(_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): 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 = list(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): 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(_Decimal(x).to_integral_value(rounding=_ROUND_HALF_UP)) _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 f'{ivalue:0{length:d}d}' if abs(ivalue) < 2**32 - 1: ivalue = ivalue & 0xffffffff else: ivalue = ivalue & 0xffffffffffffffff outdigits = [] while ivalue > 0 or ivalue < -1: ivalue, digit = divmod(ivalue, base) outdigits.append(int(digit)) outdigits = [_radixDigits[index] for index in outdigits] # zero-pad if needed 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: clParGet(paramname, prompt=0) return 1 except IrafError: # 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(f"Illegal field `{f}' in IRAF variable imextn") 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 = _io.StringIO() serr = _io.StringIO() from . import iraf 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: from . import iraf getattr(iraf, taskname) return 1 except AttributeError: # 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: # 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): 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(f"Illegal boolean value {repr(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: from . import iraf line = eval(line, {'iraf': 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: ' + repr(list(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(f"Struct type param `{namelist[i]}' " "must be the final argument to scan") # 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(f"Bug: line '{line}' pattern '{pat}' failed") iend = mm.end() if line[-1:] == '\n': cmd = namelist[i] + ' = ' + repr(line[iend:-1]) else: cmd = namelist[i] + ' = ' + repr(line[iend:]) elif strconv[i]: cmd = namelist[i] + ' = ' + strconv[i] + '(' + repr(f[i]) + ')' else: cmd = namelist[i] + ' = ' + repr(f[i]) try: exec(cmd, theLocals) n_actual += 1 except ValueError: break _nscan = n_actual return n_actual def fscanf(theLocals, line, format, *namelist, **kw): """fscanf function sets parameters from a string/list parameter with format Implementation is similar to fscan but is a bit simpler because special struct handling is not needed. Does not allow strconv keyword. Returns number of arguments set to new values, which may be fewer than the number of variables if an unexpected character is encountered in 'line'. If there are too few space-delimited arguments on the input line, it does not set all the arguments. Returns EOF on end-of-file. """ # get the value of the line (which may be a variable, string literal, # expression, or an IRAF list parameter) global _nscan try: from . import iraf line = eval(line, {'iraf': iraf}, theLocals) # format also needs to be evaluated format = eval(format, theLocals) except EOFError: _weirdEOF(theLocals, namelist) _nscan = 0 return EOF if sscanf is 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: ' + repr(list(kw.keys()))) n_actual = 0 # this will be the actual number of values converted for i in range(n): cmd = namelist[i] + ' = ' + repr(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(f"Struct type param `{namelist[0]}' " "must be the final argument to scan") # 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] = f'getParObject({repr(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 as ex: print('iraf.scan exception: ' + str(ex)) finally: 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 as ex: print('iraf.scanf exception: ' + str(ex)) finally: 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(f"set {keyword}={svalue}\n") _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)) 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: ' + repr(list(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('{} ncols={} nlines={}' .format(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 ('nlines' not in kw) and ('ncols' not 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(f"Warning: Could not find task {taskname} for tpar\n") @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(f"Warning: Could not find task {taskname} for lpar\n") @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: ' + repr(list(kw.keys()))) for taskname in args: try: taskname.dParam(cl=cl) except AttributeError: try: getTask(taskname).dParam(cl=cl) except (KeyError, TypeError): _writeError(f"Warning: Could not find task {taskname} for dpar\n") @handleRedirAndSaveKwds def update(*args): """Update task parameters on disk""" for taskname in args: try: getTask(taskname).saveParList() except KeyError: _writeError(f"Warning: Could not find task {taskname} for update") @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: ' + repr(list(kw.keys()))) for taskname in args: try: # maybe it is an IRAF task name getTask(taskname).unlearn() except KeyError: 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 ' f'to unlearn for task "{taskname}.\n"' 'None were deleted. Please review and move/' f'delete these files:\n\n\t' + '\n\t'.join(ans) + f'\n\nor type "unlearn {taskname} force=yes"') except _teal.cfgpars.NoCfgFileError: _writeError(f"Warning: Could not find task {taskname} to unlearn") @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 = list(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 = _io.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 " f"`{pkgname}' to `{spkgname}'") pkgname = spkgname if len(kw): raise TypeError('unexpected keyword argument: ' + repr(list(kw.keys()))) # execute code in a new namespace (including PkgName, PkgBinary) efilename = Expand(filename) namespace = { 'PkgName': pkgname, 'PkgBinary': pkgbinary, '__file__': efilename } exec(compile(open(efilename, "rb").read(), efilename, 'exec'), 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(f"The {cmd} task has not been implemented") @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 pyraf.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: ' + repr(list(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): # input is a string -- stick it in a StringIO buffer stdin = _io.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: _writeError(f"Warning: Could not find task {taskname} to hide") # 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<taskprefix>\$?)' + \ r'(?P<taskname>[a-zA-Z_][a-zA-Z0-9_]*)' + \ r'(?P<tasksuffix>\.(?: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 " f"`{pkgname}' to `{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 = list(kw.keys())[0] value = kw[s] # To handle when actual CL code is given, not a file name, we will # replace the code with a reader if iscmdstring: value = _io.StringIO(value) # 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(f"Illegal task name `{tlist}'") name = mtl.group('taskname') prefix = mtl.group('taskprefix') suffix = mtl.group('tasksuffix') 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: ' + repr(list(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 pkgname not in printed: printed[pkgname] = 1 print(f' {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, " f"changing `{pkgname}' to `{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(f"Package `{pkgname}' not defined") # 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(f"Warning: CL task `{pkgname}' apparently is " "a package") # Make sure that this is the current package, even # if another package was loaded in the package script # but before the package statement if loadedPath[-1] is not pkg: loadedPath.append(pkg) rv1 = (pkgname, bin or PkgBinary) finally: rv = redirReset(resetList, closeFHList) # return output as array of strings if not None, else return name,bin return rv or rv1 @handleRedirAndSaveKwds def clPrint(*args): """CL print command -- emulates CL spacing and uses redirection keywords""" nargs = len(args) for n, arg in enumerate(args, start=1): print(arg, end='') # add separator space, except after string arguments and at the end if n < nargs and not isinstance(arg, str): print(end=' ') print() # printf format conversion utilities def _quietConv(w, d, c, args, i): """Format codes that are quietly converted to %s""" return f"%{w}s" def _boolConv(w, d, c, args, i): """Boolean gets converted to upper case before printing""" args[i] = str(args[i]).upper() return f"%{w}s" def _badConv(w, d, c, args, i): """Format codes that are converted to %s with warning""" _writeError(f"Warning: printf cannot handle format '%{w + d + c}', " f"using '%{w}s' instead\n") return f"%{w}s" def _hConv(w, d, c, args, i): """Handle %h %m %H %M dd:mm:ss.s formats""" if i < len(args): try: if d[1:]: digits = int(d[1:]) else: digits = 1 if c in "HM": # capital letters convert from degrees to hours (undocumented) value = args[i] / 15.0 else: value = args[i] args[i] = clDms(value, digits=digits, seconds=c not in "mM") except ValueError: pass return f"%{w}s" def _rConv(w, d, c, args, i): """Handle W.DrN general radix format""" if i < len(args): try: base = int(c[-1]) except ValueError: base = 10 if w[:1] == "0": # add leading zeros args[i] = radix(args[i], base, length=int(w)) else: args[i] = radix(args[i], base) return f"%{w}s" def _wConv(w, d, c, args, i): """Handle %w format, which is supposed to generate spaces""" if i < len(args) and not w: # number of spaces comes from argument if args[i] == INDEF: w = 0 else: try: w = int(args[i]) except ValueError: w = 0 args[i] = "" return f"%{w}s" # pattern matching %w.dc where c is single letter format code _reFormat = _re.compile(r"%(?P<w>-?\d*)(?P<d>(?:\.\d*)?)(?P<c>[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 as e: raise IrafError(str(e)) except TypeError as e: raise IrafError(f'{str(e)}\nFormat/datatype mismatch in printf ' f'(format is {repr(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): 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(f'chdir {edir}\n') except (IrafError, OSError): try: edir = Expand(directory + '$') _os.chdir(edir) _backDir = _newBack _irafexecute.processCache.setenv(f'chdir {edir}\n') except (IrafError, OSError): raise IrafError(f"Cannot change directory to `{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(f'chdir {_backDir}\n') _backDir = _newBack def error(errno=0, errmsg='', task="error", _save=False, suppress=True): """Print error message""" e = IrafError(f"ERROR: {errmsg}\n", 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 import __main__ if verbose: vmode = ' (verbose)' else: vmode = '' print(f'Entering CL-compatibility{vmode} mode...') # 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 pyraf.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 = input(prompt) # simple continuation escape handling while line[-1:] == '\\': line = line + '\n' + input(prompt2) line = line.strip() if line in _exitCommands: break elif line[:2] == '!P': # Python escape -- execute Python code exec(line[2:].strip(), theLocals) elif line and (line[0] != '#'): code = clExecute(line, locals=theLocals, mode='single', local_vars_dict=local_vars_dict, local_vars_list=local_vars_list) if logfile is not None: # log CL code as comment cllines = line.split('\n') for oneline in cllines: logfile.write('# ' + oneline + '\n') logfile.write(code) logfile.flush() if verbose: print('----- Python -----') print(code, end=' ') print('------------------') except EOFError: break except KeyboardInterrupt: _writeError( "Use `logout' or `.exit' to exit CL-compatibility mode") except: _sys.stdout.flush() traceback.print_exc() print() print('Leaving CL-compatibility mode...') # ----------------------------------------------------- # clArray: IRAF array class with type checking # Note that subscripts start zero, in Python style -- # the CL-to-Python translation takes care of the offset # in CL code, and Python code should use zero-based # subscripts. # ----------------------------------------------------- def clArray(array_size, datatype, name="<anonymous>", 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 as e: raise ValueError(f"Error creating Cl array `{name}'\n{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=None, local_vars_list=None, verbose=0, **kw): """Execute a single cl statement""" # handle redirection keywords redirKW, closeFHList = redirProcess(kw) if len(kw): raise TypeError('unexpected keyword argument: ' + repr(list(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 = f"CL{_clExecuteCount}" scriptname = f"<CL script {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<varname>[^$]*)\$') # search for string embedded in parentheses __re_var_paren = _re.compile(r'\((?P<varname>[^()]*)\)') 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(f"Undefined variable `{varname}' " f"in string `{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(f"Undefined variable `{varname}' " f"in string `{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 " f"`{pkgname}' to `{spkgname}'") pkgname = spkgname staskname = taskname.replace('.', '_') if staskname != taskname: _writeError("Warning: `.' illegal in task name, changing " f"`{taskname}' to `{staskname}'") taskname = staskname if suffix == '.pkg': return IrafPkgFactory(prefix, taskname, suffix, value, pkgname, pkgbinary, redefine=redefine) if isinstance(value, str): ext = _os.path.splitext(value)[1] else: # Handle the case of CL command strings (no file, but StringIO) ext = '.cl' 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(f"Warning: `{taskname}' is not a defined task") 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(f"Warning: `{fullname}' is a task redefinition") 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(f"Warning: `{taskname}' is not a defined task") 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(f"Warning: `{fullname}' is a task redefinition") 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(f"Warning: `{taskname}' is not a defined task") 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 " f"`{taskname}' was not redefined") return pkg else: if not redefine: _writeError(f"Warning: `{taskname}' is a task redefinition") _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 OSError(f"Illegal value `{value}' for " f"{key} redirection") else: # expand IRAF variables value = Expand(value) if outputFlag: # output file # check to see if it is dev$null if isNullFile(value): value = _os.devnull elif "w" in openArgs and \ envget("clobber", "") != yes and \ _os.path.exists(value): # don't overwrite unless clobber is set raise OSError(f"Output file `{value}' already exists") openArgs = {'mode': openArgs} if 'b' not in openArgs['mode']: openArgs['errors'] = 'ignore' 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(f"{key} redirection must be from a file " f"handle or string\nValue is `{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 = _io.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(f"{key} redirection must be to a file " f"handle or string\nValue is 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 = _io.StringIO(s) # close this when we're done closeFHList.append(fh) except TypeError: raise IrafError(f"{key} redirection must be from a " "sequence of strings\n") else: # must be a file handle if outputFlag: if not hasattr(value, 'write'): raise IrafError(f"{key} redirection must be to a file " f"handle or string\nValue is `{value}'") elif not hasattr(value, 'read'): raise IrafError(f"{key} redirection must be from a file " f"handle or string\nValue is `{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 'stdout' not 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 io.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 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafgwcs.py�����������������������������������������������������������������������0000644�0001750�0001750�00000032523�14214070451�014666� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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. """ import numpy import math from .tools.irafglobals import IrafError from . 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 is None: return # been here already # Given a value for _WCS_RECORD_SIZE ? if forceResetTo: if forceResetTo not 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 __bool__(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 range(WCS_SLOTS): record = wcsStruct[_WCS_RECORD_SIZE * i:_WCS_RECORD_SIZE * (i + 1)] # read 8 4-byte floats from beginning of record fvals = numpy.frombuffer(record[:8 * SZ].tobytes(), 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.frombuffer(record[8 * SZ:11 * SZ].tobytes(), 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 = b'\x00\x00\x00\x00' if _IRAF64BIT: pad = b'\x00\x00\x00\x00\x00\x00\x00\x00' for i in range(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 = b'' # 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.frombuffer(farr.tobytes()+iarr.tobytes()+pad, numpy.int16) return wcsStruct.tobytes() 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 range(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 range(WCS_SLOTS): wcs.append(wcsarr) return wcs �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafhelp.py�����������������������������������������������������������������������0000644�0001750�0001750�00000034722�14214070451�014656� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""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). R. White, 1999 September 23 """ import __main__ import re import os import sys import types import io import webbrowser from inspect import signature from .tools.irafglobals import IrafError, IrafTask, IrafPkg from . import iraf from .tools import minmatch, irafutils import numpy _numpyArrayType = numpy.ndarray _MODULE = 0 _FUNCTION = 1 _METHOD = 2 _OTHER = 3 _functionTypes = (types.BuiltinFunctionType, types.FunctionType, types.LambdaType) _methodTypes = (types.BuiltinMethodType, types.MethodType) _listTypes = (list, tuple, dict) _numericTypes = (float, int, 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 typstr = str(type(an_obj)) # the following logic works, as PyRAF users expect, in both v2 and v3 return typstr=="<type 'instance'>" or \ (typstr.startswith("<class '") and ('.' in typstr)) def help(an_obj=__main__, variables=1, functions=1, modules=1, tasks=0, packages=0, hidden=0, padchars=16, regexp=None, html=0, **kw): """List the type and value of all the variables in the specified object. - help() with no arguments will list all the defined variables. - help("taskname") or help(IrafTaskObject) displays IRAF help for the task - help(object) where object is a module, instance, etc., will display information on the attributes and methods of the object. - help(function) will give the calling sequence for the function. Optional keyword arguments specify what information is to be printed. The keywords can be abbreviated: 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 attr- ibutes of sys that start with std. All the re patterns can be used. Other keywords are passed on to the IRAF help task if it is called. """ # handle I/O redirection keywords redirKW, closeFHList = iraf.redirProcess(kw) if '_save' in kw: del kw['_save'] # get the keywords using minimum-match irafkw = {} for key in kw.keys(): try: fullkey = _kwdict[key] if fullkey in _irafkwdict: # this is an IRAF help keyword irafkw[fullkey] = kw[key] else: # this is a Python help function keyword exec(fullkey + ' = ' + repr(kw[key])) except KeyError as e: raise e.__class__("Error in keyword " + key + "\n" + str(e)) resetList = iraf.redirApply(redirKW) # try block for I/O redirection try: _help(an_obj, variables, functions, modules, tasks, packages, hidden, padchars, regexp, html, irafkw) finally: rv = iraf.redirReset(resetList, closeFHList) return rv def _help(an_obj, variables, functions, modules, tasks, packages, hidden, padchars, regexp, html, irafkw): # for IrafTask object, display help and also print info on the object # for string parameter, if it looks like a task name try getting help # on it too (XXX this is a bit risky, but I suppose people will not # often be asking for help with simple strings as an argument...) if isinstance(an_obj, IrafTask): if _printIrafHelp(an_obj, html, irafkw): return if isinstance(an_obj, str): # Python task names if an_obj in sys.modules: # e.g. help drizzlepac theMod = sys.modules[an_obj] if hasattr(theMod, 'help'): theMod.help() return # sub-module Python task names else: # e.g. help astrodrizzle (when really drizzlepac.astrodrizzle) keyMatches = \ [m for m in sys.modules.keys() if m.endswith('.'+an_obj)] for theKey in keyMatches: theMod = sys.modules[theKey] if hasattr(theMod, 'help'): theMod.help() return # IRAF task names if re.match(r'[A-Za-z_][A-Za-z0-9_.]*$', an_obj) or \ (re.match(r'[^\0]*$', an_obj) and os.path.exists(iraf.Expand(an_obj, noerror=1))): if _printIrafHelp(an_obj, html, irafkw): return vlist = None if not isinstance(an_obj, _allSingleTypes): try: vlist = vars(an_obj) except TypeError: pass if vlist is None: # simple object with no vars() _valueHelp(an_obj, padchars) return # look inside the object tasklist, pkglist, functionlist, methodlist, modulelist, otherlist = \ _getContents(vlist, regexp, an_obj) if _isinstancetype(an_obj): # for instances, print a help line for the object itself first _valueHelp(an_obj, padchars=0) if modules and modulelist: # modules get listed in simple column format print("Modules:") irafutils.printCols([x[0] for x in modulelist]) print() if functions and functionlist: _printValueList(functionlist, hidden, padchars) if functions and methodlist: _printValueList(methodlist, hidden, padchars) if variables and otherlist: _printValueList(otherlist, hidden, padchars) # IRAF packages and tasks get listed in simple column format if packages and pkglist: print("IRAF Packages:") irafutils.printCols(pkglist) print() if tasks and tasklist: print("IRAF Tasks:") irafutils.printCols(tasklist) print() if _isinstancetype(an_obj) and functions: # for instances, call recursively to list class methods help(an_obj.__class__, functions=functions, tasks=tasks, packages=packages, variables=variables, hidden=hidden, padchars=padchars, regexp=regexp) # ------------------------------------ # helper functions # ------------------------------------ # return 1 if they handle the object, 0 if they don't def _printIrafHelp(an_obj, html, irafkw): if html: _htmlHelp(an_obj) return 0 else: return _irafHelp(an_obj, irafkw) def _valueHelp(an_obj, padchars): # just print info on the object itself vstr = _valueString(an_obj, verbose=1) try: name = an_obj.__name__ except AttributeError: name = '' name = name + (padchars - len(name)) * " " if name and len(name.strip()) > 0: print(name, ":", vstr) else: # omit the colon if name is null print(vstr) def _getContents(vlist, regexp, an_obj): # Make one pass through names getting the type and sort order # Also split IrafTask and IrafPkg objects into separate lists and # look into base classes if object is a class. # Returns lists of various types of included objects if regexp: re_check = re.compile(regexp) tasklist = [] pkglist = [] # lists for functions, modules, other types functionlist = [] methodlist = [] modulelist = [] otherlist = [] sortlist = 4 * [None] sortlist[_FUNCTION] = functionlist sortlist[_METHOD] = methodlist sortlist[_MODULE] = modulelist sortlist[_OTHER] = otherlist namedict = {} names = vlist.keys() for vname in names: if (regexp is None) or re_check.match(vname): value = vlist[vname] if isinstance(value, IrafPkg): pkglist.append(vname + '/') elif isinstance(value, IrafTask): tasklist.append(vname) else: vorder = _sortOrder(type(value)) sortlist[vorder].append((vname, value)) namedict[vname] = 1 # add methods from base classes if this is a class if isinstance(an_obj, type(object)): # i.e. "<class 'type'>" classlist = list(an_obj.__bases__) for c in classlist: classlist.extend(list(c.__bases__)) for vname, value in vars(c).items(): if vname not in namedict and ((regexp is None) or re_check.match(vname)): vorder = _sortOrder(type(value)) sortlist[vorder].append((vname, value)) namedict[vname] = 1 # sort into alphabetical order by name tasklist.sort() pkglist.sort() functionlist.sort() methodlist.sort() modulelist.sort() otherlist.sort() return tasklist, pkglist, functionlist, methodlist, modulelist, otherlist def _printValueList(varlist, hidden, padchars): # special hidden methods to keep (because they are widely useful) _specialHidden = ['__init__', '__call__'] for vname, value in varlist: if (hidden or vname[0:1] != '_' or vname in _specialHidden): vstr = _valueString(value) # pad name to padchars chars if shorter if len(vname) < padchars: vname = vname + (padchars - len(vname)) * " " print(vname, ":", vstr) def _sortOrder(type): if issubclass(type, types.ModuleType): v = _MODULE elif issubclass(type, _functionTypes): v = _FUNCTION elif issubclass(type, _methodTypes): v = _METHOD else: v = _OTHER return v def _valueString(value, verbose=0): """Returns name and, for some types, value of the variable as a string.""" t = type(value) vstr = t.__name__ if issubclass(t, str): if len(value) > 42: vstr += ", value = " + repr(value[:39]) + '...' else: vstr += ", value = " + repr(value) elif issubclass(t, _listTypes): return f"{vstr} [{len(value):d} entries]" elif issubclass(t, io.IOBase): vstr += ", " + repr(value) elif issubclass(t, _numericTypes): vstr += ", value = " + repr(value) elif _isinstancetype(value): cls = value.__class__ if cls.__module__ == '__main__': vstr = 'instance of class ' + cls.__name__ else: vstr = 'instance of class ' + cls.__module__ + '.' + cls.__name__ elif issubclass(t, _functionTypes + _methodTypes): try: vstr += ' ' + value.__name__ + str(signature(value)) if verbose and value.__doc__: vstr += "\n" + value.__doc__ except (AttributeError, TypeError): # oh well, just have to live with type string alone pass elif issubclass(t, _numpyArrayType): vstr += " " + str(value.dtype) + "[" for k in range(len(value.shape)): if k: vstr += "," + repr(value.shape[k]) else: vstr += repr(value.shape[k]) vstr += "]" else: # default -- just return the type pass return vstr def _irafHelp(taskname, irafkw): """Display IRAF help for given task. Task can be either a name or an IrafTask object. Returns 1 on success or 0 on failure.""" if isinstance(taskname, IrafTask): taskname = taskname.getName() else: # expand IRAF variables in case this is name of a help file taskname = iraf.Expand(taskname, noerror=1) try: if 'page' not in irafkw: irafkw['page'] = 1 iraf.system.help(taskname, **irafkw) return 1 except IrafError as e: print(str(e)) return 0 _HelpURL = "https://iraf.readthedocs.io/en/latest/tasks/by-name" def _htmlHelp(taskname): """Display HTML help for given IRAF task in a browser. Task can be either a name or an IrafTask object. """ if isinstance(taskname, IrafTask): taskname = taskname.getName() url = f"{_HelpURL}/{taskname}.html" webbrowser.open(url) ����������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafimcur.py����������������������������������������������������������������������0000644�0001750�0001750�00000005452�14214070451�015043� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""irafimcur.py: image cursor interaction Read the cursor position from stdimage image display device (DS9, SAOIMAGE or XIMTOOL) and return a string compatible with IRAF's imcur parameter. """ import sys from .tools import irafutils from .tools.irafglobals import Verbose, IrafError from . import irafdisplay from . import gwm from . import iraf # dictionary of devices to support multiple active displays _devices = {} def _getDevice(displayname=None): """Get device object for this display""" if displayname is None: displayname = iraf.envget("stdimage") try: return _devices[displayname] except KeyError: pass # look up display info in graphcap try: device = gwm.gki.getGraphcap()[displayname] dd = device['DD'].split(',') if len(dd) > 1 and dd[1] != '': imtdev = f'fifo:{dd[1]}i:{dd[1]}o' else: imtdev = None # multiple stdimage/graphcap entries can share the same device if imtdev not in _devices: _devices[imtdev] = irafdisplay.ImageDisplayProxy(imtdev) device = _devices[displayname] = _devices[imtdev] return device except (KeyError, OSError): pass # last gasp is to assume display is an imtdev string try: device = _devices[displayname] = irafdisplay.ImageDisplayProxy( displayname) return device except (ValueError, OSError): pass raise IrafError(f"Unable to open image display `{displayname}'\n") def imcur(displayname=None): """Read image cursor and return string expected for IRAF's imcur parameter If key pressed is colon, also prompts for additional string input. Raises EOFError if ^D or ^Z is typed and IrafError on other errors. The optional display argument specifies the name of the display to use (default is the display specified in stdimage). """ try: # give kernel a chance to do anything it needs right before imcur gkrnl = gwm.getActiveGraphicsWindow() if gkrnl: gkrnl.pre_imcur() # get device device = _getDevice(displayname) # Read cursor position at keystroke result = device.readCursor() if Verbose > 1: sys.__stdout__.write(f"{result}\n") sys.__stdout__.flush() if result == 'EOF': raise EOFError() x, y, wcs, key = result.split() if key in [r'\004', r'\032']: # ctrl-D and ctrl-Z are treated as EOF # Should ctrl-C raise a KeyboardInterrupt? raise EOFError() elif key == ':': sys.stdout.write(": ") sys.stdout.flush() result = result + ' ' + irafutils.tkreadline()[:-1] return result except OSError as error: raise IrafError(str(error)) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafimport.py���������������������������������������������������������������������0000644�0001750�0001750�00000013723�14214070451�015236� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""module irafimport.py -- modify import mechanism Modify module import mechanism so that (1) 'from iraf import pkg' automatically loads the IRAF package 'pkg' (2) 'import iraf' returns a wrapped module instance that allows minimum-match access to task names (e.g. iraf.imhead, not just iraf.imheader) Assumes that all IRAF tasks and packages are accessible as iraf module attributes. Only affects imports of iraf module. R. White, 1999 August 17 """ import builtins import sys from .tools import minmatch IMPORT_DEBUG = False # Save the original hooks; replaced at bottom of module... _originalImport = builtins.__import__ import importlib _originalReload = importlib.reload def restoreBuiltins(): """ Called before exiting pyraf - this puts import and reload back. """ builtins.__import__ = _originalImport importlib.reload = _originalReload def _irafImport(name, globals={}, locals={}, fromlist=[], level=-1): if IMPORT_DEBUG: print("irafimport called: " + name + ", " + str(fromlist) + ", " + str(level)) # do first: the default value for level changed to 0 as of Python 3.3 if level < 0: level = 0 # e.g. "from iraf import stsdas, noao" or "from .iraf import noao" if fromlist and (name in ["iraf", "pyraf.iraf", ".iraf"]): for task in fromlist: pkg = the_iraf_module.getPkg(task, found=1) if pkg is not None and not pkg.isLoaded(): pkg.run(_doprint=0, _hush=1) # must return a module for 'from' import if IMPORT_DEBUG: print("irafimport: case: from " + name + " import " + str(fromlist)) return _irafModuleProxy.module # e.g. "import iraf" or "from . import iraf" (fromlist is a list OR tuple) # (extra Python2 cases are not used in PY3K - double check this) if not fromlist and name == 'iraf': if IMPORT_DEBUG: print("irafimport: iraf case: n=" + name + ", fl=" + str(fromlist) + ", l=" + str(level)) return _irafModuleProxy # e.g. "import pyraf.iraf" (return module is for pyraf, not iraf) if not fromlist and name == 'pyraf.iraf' and level == 0: assert 'pyraf' in sys.modules, 'Unexpected import error' if IMPORT_DEBUG: print("irafimport: pyraf.iraf case: n=" + name + ", fl=" + str(fromlist) + ", l=" + str(level) + ", will modify pyraf module") # builtin import below will return pyraf module, after having set up an # attr of it called 'iraf' which is the iraf module. Instead we want # to set the attr to be our proxy (this case maybe unused in Python 2). retval = sys.modules['pyraf'] retval.iraf = _irafModuleProxy return retval # ALL OTHER CASES (PASS-THROUGH) # e.g. "import sys" or "import stsci.tools.alert" # e.g. "import pyraf" or "from pyraf import wutil, gki" # e.g. Note! "import os, sys, re, glob" calls this 4 separate times, but # "from . import gki, gwm, iraf" is only a single call here! hadIrafInList = fromlist and 'iraf' in fromlist and name == '' and level > 0 if IMPORT_DEBUG: print("irafimport - PASSTHRU: n=" + name + ", fl=" + str(fromlist) + ", l=" + str(level)) retval = _originalImport(name, globals, locals, fromlist, level) if hadIrafInList: # Use case is: "from . import gki, gwm, iraf" # Overwrite with our proxy (see pyraf.iraf case) retval.__setattr__('iraf', _irafModuleProxy) return retval def _irafReload(module): if isinstance(module, _irafModuleClass): # XXX Not sure this is correct module.module = _originalReload(module.module) return module else: return _originalReload(module) class _irafModuleClass: """Proxy for iraf module that makes tasks appear as attributes""" def __init__(self): self.__dict__['module'] = None def _moduleInit(self): global the_iraf_module self.__dict__['module'] = the_iraf_module self.__dict__['__name__'] = the_iraf_module.__name__ # create minmatch dictionary of current module contents self.__dict__['mmdict'] = minmatch.MinMatchDict(vars(self.module)) def __getattr__(self, attr): if self.module is None: self._moduleInit() # first try getting this attribute directly from the usual module try: return getattr(self.module, attr) except AttributeError: pass # if that fails, try getting a task with this name try: return self.module.getTask(attr) except minmatch.AmbiguousKeyError as e: raise AttributeError(str(e)) except KeyError: pass # last try is minimum match dictionary of rest of module contents try: return self.mmdict[attr] except KeyError: raise AttributeError(f"Undefined IRAF task `{attr}'") def __setattr__(self, attr, value): # add an attribute to the module itself setattr(self.module, attr, value) self.mmdict.add(attr, value) def getAllMatches(self, taskname): """Get list of names of all tasks that may match taskname Useful for command completion. """ if self.module is None: self._moduleInit() if taskname == "": matches = list(self.mmdict.keys()) else: matches = self.mmdict.getallkeys(taskname, []) matches.extend(self.module.getAllTasks(taskname)) return matches # Install our hooks builtins.__import__ = _irafImport importlib.reload = _irafReload # create the module proxy _irafModuleProxy = _irafModuleClass() # import iraf module using original mechanism # necessary as of Python 3.3+ : try "import pyraf.iraf" pyrafmod = _originalImport('pyraf.iraf', globals(), locals(), []) the_iraf_module = pyrafmod.iraf # leaving if IMPORT_DEBUG: print("irafimport: passed final import") ���������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafinst.py�����������������������������������������������������������������������0000644�0001750�0001750�00000002564�14214070451�014702� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" module irafinst.py - Defines bare-bones functionality needed when IRAF is not present. This is NOT a replacement for any major parts of IRAF. For now, in general, we assume that IRAF exists until we are told otherwise. Obviously, this module should refrain as much as possible from importing any IRAF related code (at least globally), since this is heavily relied upon in non-IRAF situations. """ import os import sys # File name prefix signal NO_IRAF_PFX = f'{os.path.dirname(__file__)}/noiraf' # Are we running without an IRAF installation? If no, EXISTS == False if sys.platform.startswith('win'): EXISTS = False else: EXISTS = 'PYRAF_NO_IRAF' not in os.environ def getIrafVer(): """ Return current IRAF version as a string """ from . import iraffunctions cltask = iraffunctions.getTask('cl') # must use default par list, in case they have a local override plist = cltask.getDefaultParList() # get the 'release' par and then get it's value release = [p.value for p in plist if p.name == 'release'] return release[0] # only 1 item in list def getIrafVerTup(): """ Return current IRAF version as a tuple (ints until last item) """ verlist = getIrafVer().split('.') outlist = [] for v in verlist: if v.isdigit(): outlist.append(int(v)) else: outlist.append(v) return tuple(outlist) ��������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafnames.py����������������������������������������������������������������������0000644�0001750�0001750�00000003663�14214070451�015031� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""module irafnames.py -- define how names of IRAF packages and tasks get included in the user's namespace. Uses a plug-in strategy so behavior can be changed. R. White, 1999 March 26 """ import __main__ from .tools import irafglobals from . import iraf def _addName(task, module): """Add a task object to the module namespace Skip if there is a collision with another name unless it is an IrafTask """ name = task.getName() if hasattr(module, name): p = getattr(module, name) else: p = None if (p is None) or isinstance(p, irafglobals.IrafTask): setattr(module, name, task) else: if irafglobals.Verbose > 0: print("Warning: " + module.__name__ + "." + name + " was not redefined as Iraf Task") # Basic namespace strategy class (does nothing) class IrafNameStrategy: def addTask(self, task): pass def addPkg(self, pkg): pass # NameClean implementation puts tasks and packages in iraf module name space # Note that since packages are also tasks, we only need to do this for tasks class IrafNameClean(IrafNameStrategy): def addTask(self, task): _addName(task, iraf) # IrafNamePkg also adds packages to __main__ name space class IrafNamePkg(IrafNameClean): def addPkg(self, pkg): _addName(pkg, __main__) # IrafNameTask puts everything (tasks and packages) in __main__ name space class IrafNameTask(IrafNameClean): def addTask(self, task): _addName(task, iraf) _addName(task, __main__) def setPkgStrategy(): global strategy strategy = IrafNamePkg() def setTaskStrategy(): global strategy strategy = IrafNameTask() def setCleanStrategy(): global strategy strategy = IrafNameClean() # define adding package names as the default behavior # setPkgStrategy() # define adding package names to iraf module only as the default behavior setCleanStrategy() �����������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafpar.py������������������������������������������������������������������������0000644�0001750�0001750�00000164376�14214070451�014521� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""irafpar.py -- parse IRAF .par files and create lists of IrafPar objects R. White, 2000 January 7 """ import copy import glob import os import re import types from .tools import minmatch, irafutils, taskpars, basicpar from .tools.irafglobals import INDEF, Verbose, yes, no from .tools.basicpar import (warning, _StringMixin, IrafPar, IrafParS, _cmdlineFlag) # also import basicpar.IrafPar* class names for cached scripts from .tools.basicpar import (IrafParB, IrafParI, IrafParR, IrafParAB, IrafParAI, IrafParAR, IrafParAS) from . import iraf # ----------------------------------------------------- # IRAF parameter factory # ----------------------------------------------------- _string_list_types = ('*struct', '*s', '*f', '*i') def IrafParFactory(fields, strict=0): """IRAF parameter factory fields is a list of the comma-separated fields (as in the .par file). Each entry is a string or None (indicating that field was omitted.) Set the strict parameter to a non-zero value to do stricter parsing (to find errors in the input.)""" # Sanity check if len(fields) < 3 or None in fields[0:3]: raise SyntaxError("At least 3 fields must be given") type = fields[1] # Handle special PyRAF/IRAF types, otherwise default to the standard types if type in _string_list_types: return IrafParLS(fields, strict) elif type == "*gcur" or type == "gcur": return IrafParGCur(fields, strict) elif type == "*imcur" or type == "imcur": return IrafParImCur(fields, strict) elif type == "*ukey" or type == "ukey": return IrafParUKey(fields, strict) elif type == "pset": return IrafParPset(fields, strict) else: return basicpar.parFactory(fields, strict) # ----------------------------------------------------- # make an IrafPar variable (another factory function, # using more descriptive notation for characteristics) # ----------------------------------------------------- # dictionary mapping verbose types to short par-file types _typedict = { 'string': 's', 'char': 's', 'file': 'f', 'struct': 'struct', 'int': 'i', 'bool': 'b', 'real': 'r', 'double': 'd', 'gcur': 'gcur', 'imcur': 'imcur', 'ukey': 'ukey', 'pset': 'pset', } def makeIrafPar(init_value, datatype=None, name="<anonymous>", mode="h", array_size=None, list_flag=0, min=None, max=None, enum=None, prompt="", strict=0, filename=None): """Create an IrafPar variable""" # Deprecation note - after 1.6 is released, remove the arg and this note if (filename is not None and len(filename) > 0 and filename != 'string_proc'): warning("Use of filename arg in makeIrafPar is rather deprecated\n" + ", filename = \'" + filename + "'", level=-1) # if init_value is already an IrafPar, just return it # XXX Could check parameters to see if they are ok if isinstance(init_value, IrafPar): return init_value # XXX Enhance this to determine datatype from init_value if it is omitted # XXX Could use _typedict.get(datatype,datatype) to allow short types to be used if datatype is None: raise ValueError("datatype must be specified") shorttype = _typedict[datatype] if array_size is None: shape = None else: shorttype = "a" + shorttype # array_size can be an integer or a tuple # get a tuple shape and make array_size the # combined size of all dimensions try: shape = tuple(array_size) except TypeError: shape = (array_size,) array_size = 1 for d in shape: array_size = array_size * d if list_flag: shorttype = "*" + shorttype # messy stuff -- construct strings like we would read # from .par file for this parameter if shape is None: # scalar parameter fields = [name, shorttype, mode, init_value, min, max, prompt] if fields[4] is None: fields[4] = enum else: # N-dimensional array parameter fields = [ name, shorttype, mode, str(len(shape)), # number of dims ] for d in shape: fields.extend([ d, # dimension "1" ]) # apparently always 1 if min is None: fields.extend([enum, max, prompt]) else: fields.extend([min, max, prompt]) if init_value is not None: if len(init_value) != array_size: raise ValueError("Initial value list does not match array " f"size for parameter `{name}'") for iv in init_value: fields.append(iv) else: fields = fields + array_size * [None] for i in range(len(fields)): if fields[i] is not None: fields[i] = str(fields[i]) try: return IrafParFactory(fields, strict=strict) except ValueError as e: errmsg = f"Bad value for parameter `{name}'\n{str(e)}" raise ValueError(errmsg) # ----------------------------------------------------- # IRAF pset parameter class # ----------------------------------------------------- class IrafParPset(IrafParS): """IRAF pset parameter class""" def __init__(self, fields, strict=0): IrafParS.__init__(self, fields, strict) # omitted pset parameters default to null string if self.value is None: self.value = "" def get(self, field=None, index=None, lpar=0, prompt=1, native=0, mode="h"): """Return pset value (IrafTask object)""" if index: raise SyntaxError("Parameter " + self.name + " is pset, cannot use index") if field: return self._getField(field) if lpar: return str(self.value) # assume there are no query or indirection pset parameters # see if parameter value has .par extension, if so, it is a file name f = self.value.split('.') if len(f) > 1 and f[-1] == 'par': # must be a file name from .iraffunctions import IrafTaskFactory irf_val = iraf.Expand(self.value) return IrafTaskFactory(taskname=irf_val.split(".")[0], value=irf_val) else: # must be a task name if self.value: # The normal case here is that the value is a task name string # so we get&return that task. There is a quirky case where in # some CL scripts (e.g. ccdproc.cl), the CL script writers use # this place as a temporarty place to store values; handle that. if self.value.startswith('<') and self.value.endswith( '>') and self.name in self.value: # don't lookup task for self.value, it is something like: # "<IrafCLTask ccdproc (mscsrc$ccdproc.cl) Pkg: mscred Bin: mscbin$>" return iraf.getTask(self.name) # this is only a safe assumption to make in a PSET else: return iraf.getTask(self.value) else: return iraf.getTask(self.name) # ----------------------------------------------------- # IRAF list parameter base class # ----------------------------------------------------- class IrafParL(_StringMixin, IrafPar): """IRAF list parameter base class""" def __init__(self, fields, strict=0): IrafPar.__init__(self, fields, strict) # filehandle for input file self.__dict__['fh'] = None # lines used to store input when not reading from a tty self.__dict__['lines'] = None # flag inidicating error message has been printed if file does not exist # message only gets printed once for each file self.__dict__['errMsg'] = 0 # omitted list parameters default to null string if self.value is None: self.value = "" # -------------------------------------------- # public methods # -------------------------------------------- def set(self, value, field=None, index=None, check=1): """Set value of this parameter from a string or other value. Field is optional parameter field (p_prompt, p_minimum, etc.) Index is optional array index (zero-based). Set check=0 to assign the value without checking to see if it is within the min-max range or in the choice list.""" if index is not None: raise SyntaxError("Parameter " + self.name + " is not an array") if field: self._setField(value, field, check=check) else: if check: self.value = self.checkValue(value) else: self.value = self._coerceValue(value) self.setChanged() # close file if it is open if self.fh: try: self.fh.close() except OSError: pass self.fh = None self.lines = None self.errMsg = 0 def get(self, field=None, index=None, lpar=0, prompt=1, native=0, mode="h"): """Return value of this parameter as a string (or in native format if native is non-zero.)""" if field: return self._getField(field, native=native, prompt=prompt) if lpar: if self.value is None and native == 0: return "" else: return self.value # assume there are no query or indirection list parameters if index is not None: raise SyntaxError("Parameter " + self.name + " is not an array") if self.value: # non-null value means we're reading from a file try: if not self.fh: self.fh = open(iraf.Expand(self.value), errors="ignore") if self.fh.isatty(): self.lines = None else: # read lines in a block # reverse to make pop more convenient & faster self.lines = self.fh.readlines() self.lines.reverse() if self.lines is None: value = self.fh.readline() elif self.lines: value = self.lines.pop() else: value = '' if not value: # EOF -- raise exception raise EOFError(f"EOF from list parameter `{self.name}'") if value[-1:] == "\n": value = value[:-1] except OSError as e: if not self.errMsg: warning(f"Unable to read values for list parameter " f"`{self.name}' from file `{self.value}'\n{str(e)}", level=-1) # only print message one time self.errMsg = 1 # fall back on default behavior if file is not readable value = self._getNextValue() else: # if self.value is null, use the special _getNextValue method # (which should always return a string) if prompt: value = self._getNextValue() else: return self.value if native: return self._coerceValue(value) else: return value # -------------------------------------------- # private methods # -------------------------------------------- # Use _getNextValue() method to implement a particular type def _getNextValue(self): """Return a string with next value""" raise RuntimeError("Bug: base class IrafParL cannot be used directly") def _getPFilename(self, native, prompt): """Get p_filename field for this parameter (returns filename)""" # XXX is this OK? should we check for self.value==None? return self.value def _getPType(self): """Get underlying datatype for this parameter Strip off '*' from list params """ return self.type[1:] # ----------------------------------------------------- # IRAF string list parameter class # ----------------------------------------------------- class IrafParLS(IrafParL): """IRAF string list parameter class""" def _getNextValue(self): """Return next string value""" # save current values (in case this got called # because filename was in error) saveVal = self.value saveErr = self.errMsg try: # get rid of default value in prompt self.value = None self.getWithPrompt() retval = self.value return retval finally: # restore original values self.value = saveVal self.errMsg = saveErr # ----------------------------------------------------- # IRAF cursor parameter class # ----------------------------------------------------- class IrafParCursor(IrafParL): """Base class for cursor parameters""" def _coerceOneValue(self, value, strict=0): if isinstance(value, IrafParCursor): return value.p_filename else: return IrafParL._coerceOneValue(self, value, strict) # ----------------------------------------------------- # IRAF gcur (graphics cursor) parameter class # ----------------------------------------------------- class IrafParGCur(IrafParCursor): """IRAF graphics cursor parameter class""" def _getNextValue(self): """Return next graphics cursor value""" from . import gki # lazy import - reduce circular imports on startup return gki.kernel.gcur() # ----------------------------------------------------- # IRAF imcur (image display cursor) parameter class # ----------------------------------------------------- class IrafParImCur(IrafParCursor): """IRAF image display cursor parameter class""" def _getNextValue(self): """Return next image display cursor value""" from . import irafimcur # lazy import - reduce circular imports on startup return irafimcur.imcur() # ----------------------------------------------------- # IRAF ukey (user typed key) parameter class # ----------------------------------------------------- class IrafParUKey(IrafParL): """IRAF user typed key parameter class""" def _getNextValue(self): """Return next typed character""" from . import irafukey # lazy import - reduce circular imports on startup return irafukey.ukey() # ----------------------------------------------------- # IRAF parameter list synchronized to disk file # ----------------------------------------------------- from . import filecache class ParCache(filecache.FileCache): """Parameter cache that updates from .par file when necessary""" def __init__(self, filename, parlist, strict=0): self.initparlist = parlist # special filename used by cl2py if filename is None or filename == 'string_proc': filename = '' try: filecache.FileCache.__init__(self, filename) except OSError: # whoops, couldn't open that file # start with a null file instead unless strict is set if strict: raise filename = '' filecache.FileCache.__init__(self, filename) def getValue(self): return self.pars, self.pardict, self.psetlist def newValue(self): """Called to create initial value""" # initparlist dominates .par file during initialization if self.initparlist is not None: self.pars = self.initparlist elif self.filename: self.pars = _readpar(self.filename) else: # create empty list if no filename is specified self.pars = [] # build auxiliary attributes from pars list self._buildFromPars() def updateValue(self): """Initialize parameter list from parameter file""" if self.filename: # .par file dominates initparlist on update self.pars = _readpar(self.filename) elif self.initparlist is not None: self.pars = self.initparlist else: # create empty list if no filename is specified self.pars = [] # build auxiliary attributes from pars list self._buildFromPars() def _buildFromPars(self): # build minmatch dictionary of all parameters, including # those in psets self.pardict = minmatch.MinMatchDict() psetlist = [] for p in self.pars: self.pardict.add(p.name, p) if isinstance(p, IrafParPset): psetlist.append(p) # add mode, $nargs to parameter list if not already present if not self.pardict.has_exact_key("mode"): p = makeIrafPar("al", name="mode", datatype="string", mode="h") self.pars.append(p) self.pardict.add(p.name, p) if not self.pardict.has_exact_key("$nargs"): p = makeIrafPar(0, name="$nargs", datatype="int", mode="h") self.pars.append(p) self.pardict.add(p.name, p) # save the list of pset parameters # Defer adding the parameters until later because saved parameter # sets may not be defined yet when restoring from save file. self.psetlist = psetlist # ----------------------------------------------------- # IRAF parameter list class # ----------------------------------------------------- # Note that all methods are mixed case and all attributes are private # (start with __) to avoid conflicts with parameter names class IrafParList(taskpars.TaskPars): """List of Iraf parameters""" def __init__(self, taskname, filename="", parlist=None): """Create a parameter list for task taskname If parlist is specified, uses it as a list of IrafPar objects. Else if filename is specified, reads a .par file. If neither is specified, generates a default list. """ self.__pars = [] self.__hasPsets = False self.__psets2merge = None # is a list when populated self.__psetLock = False self.__filename = filename self.__name = taskname self.__filecache = ParCache(filename, parlist) # initialize parameter list self.Update() def Update(self): """Check to make sure this list is in sync with parameter file""" self.__pars, self.__pardict, self.__psets2merge = \ self.__filecache.get() if self.__psets2merge: self.__addPsetParams() def setFilename(self, filename): """Change filename and create ParCache object Retains current parameter values until an unlearn is done """ if hasattr(filename, 'name') and hasattr(filename, 'read'): filename = filename.name if isinstance(filename, str): root, ext = os.path.splitext(filename) if ext != ".par": # Only .par files are used as basis for parameter cache -- see if there # is one # Note that parameters specified in CL scripts are automatically updated # when the script changes filename = root + ".par" if not os.path.exists(filename): filename = "" else: filename = "" if self.__filename != filename: if filename: # it is an error if this file does not exist self.__filecache = ParCache(filename, None, strict=1) else: # for null filename, default parameter list is fixed self.__filecache = ParCache(filename, self.__pars) self.__filename = filename def __addPsetParams(self): """ Merge pset parameters into the parameter lists. Developer note - the original intention of this may have been to ensure that the pset par which appears in this list is NOT a copy of the original par (from the pset) but a reference to the same object, and if so, that would make things work smoothly, but it was found in Feb of 2013 that this is not happening correctly, and may be an unsafe plan. Therefore the code was changed to allow clients to access both copies; see getParObjects() and any related code. """ # return immediately if they have already been added or # if we are in the midst of a recursive call tree if self.__psetLock or self.__psets2merge is None: return # otherwise, merge in any PSETs if len(self.__psets2merge) > 0: self.__hasPsets = True # never reset self.__psetLock = True # prevent us from coming in recursively # Work from the pset's pardict because then we get # parameters from nested psets too for p in self.__psets2merge: # silently ignore parameters from psets that already are defined psetdict = p.get().getParDict() for pname in psetdict.keys(): if not self.__pardict.has_exact_key(pname): self.__pardict.add(pname, psetdict[pname]) # back to normal state self.__psets2merge = None self.__psetLock = False def addParam(self, p): """Add a parameter to the list""" if not isinstance(p, IrafPar): t = type(p) if issubclass(t, types.InstanceType): tname = p.__class__.__name__ else: tname = t.__name__ raise TypeError("Parameter must be of type IrafPar (value: " + tname + ", type: " + str(t) + ", object: " + repr(p) + ")") elif self.__pardict.has_exact_key(p.name): if p.name in ["$nargs", "mode"]: # allow substitution of these default parameters self.__pardict[p.name] = p for i in range(len(self.__pars)): j = -i - 1 if self.__pars[j].name == p.name: self.__pars[j] = p return else: raise RuntimeError(f"Bug: parameter `{name}' is in " "dictionary __pardict but not in " "list __pars??") raise ValueError(f"Parameter named `{p.name}' is already defined") # add it just before the mode and $nargs parameters (if present) j = -1 for i in range(len(self.__pars)): j = -i - 1 if self.__pars[j].name not in ["$nargs", "mode"]: break else: j = -len(self.__pars) - 1 self.__pars.insert(len(self.__pars) + j + 1, p) self.__pardict.add(p.name, p) if isinstance(p, IrafParPset): # parameters from this pset will be added too if self.__psets2merge is None: # add immediately self.__psets2merge = [p] self.__addPsetParams() else: # just add to the pset list self.__psets2merge.append(p) # can't call __addPsetParams here as we may now be inside a call def isConsistent(self, other): """Compare two IrafParLists for consistency Returns true if lists are consistent, false if inconsistent. Only checks immutable param characteristics (name & type). Allows hidden parameters to be in any order, but requires non-hidden parameters to be in identical order. """ if not isinstance(other, self.__class__): if Verbose > 0: print(f'Comparison list is not a {self.__class__.__name__}') return 0 # compare minimal set of parameter attributes thislist = self._getConsistentList() otherlist = other._getConsistentList() if thislist == otherlist: return 1 else: if Verbose > 0: _printVerboseDiff(thislist, otherlist) return 0 def _getConsistentList(self): """Return simplified parameter dictionary used for consistency check Dictionary is keyed by param name, with value of type and (for non-hidden parameters) sequence number. """ dpar = {} j = 0 hflag = -1 for par in self.__pars: if par.mode == "h": dpar[par.name] = (par.type, hflag) else: dpar[par.name] = (par.type, j) j = j + 1 return dpar def _dlen(self): """ For diagnostic use only: return length of class attr name dict. """ return len(self.__dict__) def clearFlags(self): """Clear all status flags for all parameters""" for p in self.__pars: p.setFlags(0) def setAllFlags(self): """Set all status flags to indicate parameters were set on cmdline""" for p in self.__pars: p.setCmdline() # parameters are accessible as attributes def __getattr__(self, name): # DBG: id(self), len(self.__dict__), "__getattr__ for: "+str(name) if name and name[0] == '_': raise AttributeError(name) try: return self.getValue(name, native=1) except SyntaxError as e: raise AttributeError(str(e)) def __setattr__(self, name, value): # DBG: id(self), len(self.__dict__), "__setattr__ for: "+str(name)+", value: "+str(value)[0:20] # hidden Python parameters go into the standard dictionary # (hope there are none of these in IRAF tasks) if name and name[0] == '_': object.__setattr__(self, name, value) else: self.setParam(name, value) def __len__(self): return len(self.__pars) # public accessor functions for attributes def hasPar(self, param): """Test existence of parameter named param""" if self.__psets2merge: self.__addPsetParams() param = irafutils.untranslateName(param) return param in self.__pardict def getFilename(self): return self.__filename def getParList(self, docopy=0): if docopy: # return copy of the list if docopy flag set pars = copy.deepcopy(self.__pars) for p in pars: p.setFlags(0) return pars else: # by default return the list itself return self.__pars def getParDict(self): if self.__psets2merge: self.__addPsetParams() return self.__pardict def getParObject(self, param): """ Returns an IrafPar object matching the name given (param). This looks only at the "top level" (which includes any duplicated PSET pars via __addPsetParams), but does not look down into PSETs. Note the difference between this and getParObjects in their different return types. """ if self.__psets2merge: self.__addPsetParams() try: param = irafutils.untranslateName(param) return self.__pardict[param] except KeyError as e: raise e.__class__("Error in parameter '" + param + "' for task " + self.__name + "\n" + str(e)) def getParObjects(self, param, typecheck=True): """ Returns all IrafPar objects matching the string name given (param), in the form of a dict like: { scopename : <IrafPar instance>, ... } where scopename is '' if par was found as a regular par in this list, or, where scopename is psetname if the par was found inside a PSET. It is possible that some dict values will actually be the same object in memory (see docs for __addPsetParams). This _will_ raise a KeyError if the given param name was not found at the "top level" (a regular par inside this par list) even if it is also in a PSET. typecheck: If multiple par objects are found, and typecheck is set to True, only the first (e.g. top level) will be returned if those par objects have a different value for their .type attribute. Otherwise all par objects found are returned in the dict. Note the difference between this and getParObject in their different return types. """ # Notes: # To accomplish the parameter setting (e.g. setParam) this calls up # all possible exact-name-matching pars in this par list, whether # they be on the "top" level with that name (param), or down in some # PSET with that name (param). If we are simply an IRAFish task, then # this is fine as we can assume the task likely does not have a par of # its own and a PSET par, both of which have the same name. Thus any # such case will acquire a copy of the PSET par at the top level. See # discussion of this in __addPsetParams(). # BUT, if we are a CL script (e.g. mscombine.cl), we could have local # vars which happen to have the same names as PSET pars. This is an # issue that we need to handle and be aware of (see typecheck arg). if self.__psets2merge: self.__addPsetParams() param = irafutils.untranslateName(param) retval = {} # First find the single "top-level" matching par try: pobj = self.__pardict[param] retval[''] = pobj except KeyError as e: raise e.__class__("Error in parameter '" + param + "' for task " + self.__name + "\n" + str(e)) # Next, see if there are any pars by this name inside any PSETs if not self.__hasPsets: return retval # There is a PSET in here somewhere... allpsets = [p for p in self.__pars if isinstance(p, IrafParPset)] for pset in allpsets: # Search the pset's pars. We definitely do NOT want a copy, # we need the originals to edit. its_task = pset.get() its_plist = its_task.getParList(docopy=0) # assume full paramname given (no min-matching inside of PSETs) matching_pars = [pp for pp in its_plist if pp.name == param] if len(matching_pars) > 1: raise RuntimeError('Unexpected multiple matches for par: ' + param + ', are: ' + str([p.name for p in matching_pars])) # found one with that name; add it to outgoing dict if len(matching_pars) > 0: addit = True if typecheck and '' in retval: # in this case we already found a top-level and we've been # asked to make sure to return only same-type matches addit = matching_pars[0].type == retval[ ''].type # attr is a char if addit: retval[pset.name] = matching_pars[0] return retval def getAllMatches(self, param): """Return list of all parameter names that may match param""" if param == "": return list(self.__pardict.keys()) else: return self.__pardict.getallkeys(param, []) def getValue(self, param, native=0, prompt=1, mode="h"): """Return value for task parameter 'param' (with min-match) If native is non-zero, returns native format for value. Default is to return a string. If prompt is zero, does not prompt for parameter. Default is to prompt for query parameters. """ par = self.getParObject(param) value = par.get(native=native, mode=mode, prompt=prompt) if isinstance(value, str) and value and value[0] == ")": # parameter indirection: ')task.param' try: task = iraf.getTask(self.__name) value = task.getParam(value[1:], native=native, mode="h") except KeyError: # if task is not known, use generic function to get param value = iraf.clParGet(value[1:], native=native, mode="h", prompt=prompt) return value def setParam(self, param, value, scope='', check=0, idxHint=None): """Set task parameter 'param' to value (with minimum-matching). scope, idxHint, and check are included for use as a task object but they are currently ignored.""" matches_dict = self.getParObjects(param) for par_obj in matches_dict.values(): par_obj.set(value) def setParList(self, *args, **kw): """Set value of multiple parameters from list""" # first undo translations that were applied to keyword names for key in list(kw.keys()): okey = key key = irafutils.untranslateName(key) if okey != key: value = kw[okey] del kw[okey] kw[key] = value # then expand all keywords to their full names and add to fullkw fullkw = {} dupl_pset_pars = [] for key in kw.keys(): # recall, kw is just simple { namestr: valstr, ... } try: # find par obj for this key # (read docs for getParObjects - note the 's') results_dict = self.getParObjects(key) # results_dict is of form: { psetname : <IrafPar instance> } # where results_dict may be (and most often is) empty string ''. # if no KeyError, then there exists a top-level entry ('') if '' not in results_dict: raise RuntimeError('No top-level match; expected KeyError') # assume results_dict[''].name.startswith(key) or .name==key # recall that key might be shortened version of par's .name param = (results_dict[''].name, '' ) # this means (paramname, [unused]) results_dict.pop('') # if there are others, then they are pars with the same name # but located down inside a PSET. So we save them for further # handling down below. for psetname in results_dict: if not results_dict[psetname].name.startswith(key): raise RuntimeError('PSET name non-match; par name: ' + key + '; got: ' + results_dict[psetname].name) dupl_pset_pars.append( (psetname, results_dict[psetname].name, key)) except KeyError as e: # Perhaps it is pset.param ? This would occur if the caller # used kwargs like gemcube(..., geofunc.axis1 = 1, ...) # (see help call #3454 for Mark Sim.) i = key.find('.') if i <= 0: raise e # recall that key[:i] might be shortened version of par's .name param = (self.getParObject(key[:i]).name, key[i + 1:]) # here param is (pset name, par name) if param in fullkw: msg_full_pname = param[0] if param[1]: msg_full_pname = '.'.join(param) # at this point, msg_full_pname is fully qualified raise SyntaxError("Multiple values given for parameter " + msg_full_pname + " in task " + self.__name) # Add it fullkw[param] = kw[key] # At this point, an example of fullkw might be: # {('extname', ''): 'mef', ('long', ''): no, ('ccdtype', ''): ''} # NOTE that the keys to this dict are EITHER in the form # (top level par name, '') -OR- (pset name, par name) # CDS June2014 - this is ugly - love to change this soon... # Now add any duplicated pars that were found, both up at top level and # down inside a PSET (saved as dupl_pset_pars list). The top level # version has already been added to fullkw, so we add the PSET version. for par_tup in dupl_pset_pars: # par_tup is of form: # (pset name (full), par name (full), par name (short/given), ) if par_tup[0:2] not in fullkw: # use par_tup[2]; its the given kw arg w/out the # identifying pset name fullkw[par_tup[0:2]] = kw[par_tup[2]] # Now add positional parameters to the keyword list, checking # for duplicates ipar = 0 for value in args: while ipar < len(self.__pars): if self.__pars[ipar].mode != "h": break ipar = ipar + 1 else: # executed if we run out of non-hidden parameters raise SyntaxError("Too many positional parameters for task " + self.__name) # at this point, ipar is set to index of next found non-hidden # par in self.__pars param = (self.__pars[ipar].name, '') if param in fullkw: # uh-oh, it was already in our fullkw list, but now we got a # positional value for it (occurs in _ccdtool; help call #5901) msg_full_pname = param[0] if param[1]: msg_full_pname = '.'.join(param) msg_val_from_kw = fullkw[param] msg_val_from_pos = value # let's say we only care if the 2 values are, in fact, different if msg_val_from_kw != msg_val_from_pos: raise SyntaxError('Both a positional value ("' + str(msg_val_from_pos) + '") and a keyword value ("' + str(msg_val_from_kw) + '") were given for parameter "' + msg_full_pname + '" in task "' + self.__name + '"') # else:, we'll now just overwite the old value with the same new value fullkw[param] = value ipar = ipar + 1 # Now set all keyword parameters ... # clear changed flags and set cmdline flags for arguments self.clearFlags() # Count number of positional parameters set on cmdline # Note that this counts positional parameters set through # keywords in $nargs -- that is different from IRAF, which # counts only non-keyword parameters. That is a bug in IRAF. nargs = 0 for key, value in fullkw.items(): param, tail = key p = self.getParObject(param) if tail: # is pset parameter - get parameter object from its task p = p.get().getParObject(tail) # what if *this* p is a IrafParPset ? skip for now, # since we think no one is doubly nesting PSETs p.set(value) p.setFlags(_cmdlineFlag) if p.mode != "h": nargs = nargs + 1 # Number of arguments on command line, $nargs, is used by some IRAF # tasks (e.g. imheader). self.setParam('$nargs', nargs) def eParam(self): from . import epar epar.epar(self) def tParam(self): from . import tpar tpar.tpar(self) def lParam(self, verbose=0): print(self.lParamStr(verbose=verbose)) def lParamStr(self, verbose=0): """List the task parameters""" retval = [] # Do the non-hidden parameters first for i in range(len(self.__pars)): p = self.__pars[i] if p.mode != 'h': if Verbose > 0 or p.name != '$nargs': retval.append(p.pretty(verbose=verbose or Verbose > 0)) # Now the hidden parameters for i in range(len(self.__pars)): p = self.__pars[i] if p.mode == 'h': if Verbose > 0 or p.name != '$nargs': retval.append(p.pretty(verbose=verbose or Verbose > 0)) return '\n'.join(retval) def dParam(self, taskname="", cl=1): """Dump the task parameters in executable form Default is to write CL version of code; if cl parameter is false, writes Python executable code instead. """ if taskname and taskname[-1:] != ".": taskname = taskname + "." for i in range(len(self.__pars)): p = self.__pars[i] if p.name != '$nargs': print(f"{taskname}{p.dpar(cl=cl)}") if cl: print("# EOF") def saveParList(self, filename=None, comment=None): """Write .par file data to filename (string or filehandle)""" if filename is None: filename = self.__filename if not filename: raise ValueError("No filename specified to save parameters") # but not if user turned off parameter writes writepars = int(iraf.envget("writepars", 1)) if writepars < 1: msg = "No parameters written to disk." print(msg) return msg # ok, go ahead and write 'em - set up file if hasattr(filename, 'write'): fh = filename else: absFileName = iraf.Expand(filename) absDir = os.path.dirname(absFileName) if len(absDir) and not os.path.isdir(absDir): os.makedirs(absDir) fh = open(absFileName, 'w') nsave = len(self.__pars) if comment: fh.write('# ' + comment + '\n') for par in self.__pars: if par.name == '$nargs': nsave = nsave - 1 else: fh.write(par.save() + '\n') if fh != filename: fh.close() return f"{nsave:d} parameters written to {filename}" elif hasattr(fh, 'name'): return f"{nsave:d} parameters written to {fh.name}" else: return f"{nsave:d} parameters written" def __getinitargs__(self): """Return parameters for __init__ call in pickle""" return (self.__name, self.__filename, self.__pars) # # These two methods were set to do nothing (they were previously # needed for pickle) but having them this way makes PY3K deepcopy # fail in an extremely difficult to diagnose way. # # def __getstate__(self): # """Return additional state for pickle""" # # nothing beyond init # return None # # def __setstate__(self, state): # """Restore additional state from pickle""" # pass def __str__(self): s = '<IrafParList ' + self.__name + ' (' + self.__filename + ') ' + \ str(len(self.__pars)) + ' parameters>' return s # these methods are provided so an IrafParList can be treated like # an IrafTask object by epar (and other modules) def getDefaultParList(self): return self.getParList() def getName(self): return self.__name def getPkgname(self): return '' def run(self, *args, **kw): pass def _printVerboseDiff(list1, list2): """Print description of differences between parameter lists""" pd1, hd1 = _extractDiffInfo(list1) pd2, hd2 = _extractDiffInfo(list2) _printHiddenDiff(pd1, hd1, pd2, hd2) # look for hidden/positional changes _printDiff(pd1, pd2, 'positional') # compare positional parameters _printDiff(hd1, hd2, 'hidden') # compare hidden parameters def _extractDiffInfo(alist): hflag = -1 pd = {} hd = {} for key, value in alist.items(): if value[1] == hflag: hd[key] = value else: pd[key] = value return (pd, hd) def _printHiddenDiff(pd1, hd1, pd2, hd2): for key in list(pd1.keys()): if key in hd2: print(f"Parameter `{key}' is hidden in list 2 but not list 1") del pd1[key] del hd2[key] for key in list(pd2.keys()): if key in hd1: print(f"Parameter `{key}' is hidden in list 1 but not list 2") del pd2[key] del hd1[key] def _printDiff(pd1, pd2, label): if pd1 == pd2: return noextra = 1 k1 = sorted(pd1.keys()) k2 = sorted(pd2.keys()) if k1 != k2: # parameter name lists differ i1 = 0 i2 = 0 noextra = 0 while i1 < len(k1) and i2 < len(k2): key1 = k1[i1] key2 = k2[i2] if key1 == key2: i1 = i1 + 1 i2 = i2 + 1 else: # one or both parameters missing if key1 not in pd2: print(f"Extra {label} parameter `{key1}' " f"(type `{pd1[key1][0]}') in list 1") # delete the extra parameter del pd1[key1] i1 = i1 + 1 if key2 not in pd1: print(f"Extra {label} parameter `{key2}' " f"(type `{pd2[key2][0]}') in list 2") del pd2[key2] i2 = i2 + 1 # other parameters must be missing while i1 < len(k1): key1 = k1[i1] print("Extra {label} parameter `{key1}' " f"(type `{pd1[key1][0]}') in list 1") del pd1[key1] i1 = i1 + 1 while i2 < len(k2): key2 = k2[i2] print(f"Extra {label} parameter `{key2}' " f"(type `{pd2[key2][0]}') in list 2") del pd2[key2] i2 = i2 + 1 # remaining parameters are in both lists # check for differing order or type, but ignore order if there # were extra parameters for key in pd1.keys(): if pd1[key] != pd2[key]: mm = [] type1, order1 = pd1[key] type2, order2 = pd2[key] if noextra and order1 != order2: mm.append("order disagreement") if type1 != type2: mm.append(f"type disagreement (`{type1}' vs. `{type2}')") print(f"Parameter `{key}': {', '.join(mm)}") # The dictionary of all special-use par files found on disk. # Each key is a tuple of (taskName, pkgName). # Each value is a list of path names. _specialUseParFileDict = None # For TASKMETA lines in par files, e.g.: '# TASKMETA: task=display package=tv' _re_taskmeta = \ re.compile(r'^# *TASKMETA *: *task *= *([^ ]*) *package *= *([^ \n]*)') def _updateSpecialParFileDict(dirToCheck=None, strict=False): """ Search the disk in the given path (or .) for special-purpose parameter files. These can have any name, end in .par, and have metadata comments which identify their associated task. This function simply fills or adds to our _specialUseParFileDict dictionary. If strict is True then any .par file found is expected to have our TASKMETA tag. """ global _specialUseParFileDict # Default state is that dictionary is created but empty if _specialUseParFileDict is None: _specialUseParFileDict = {} # If the caller gave us a dirToCheck, use only it, otherwise check the # usual places (which calls us recursively). if dirToCheck is None: # Check the auxilliary par dir uparmAux = iraf.envget("uparm_aux", "") if 'UPARM_AUX' in os.environ: uparmAux = os.environ['UPARM_AUX'] if len(uparmAux) > 0: _updateSpecialParFileDict(dirToCheck=uparmAux, strict=True) # If the _updateSpecialParFileDict processing is found to be # be taking too long, we could easily add a global flag here like # _alreadyCheckedUparmAux = True # Also check the current directory _updateSpecialParFileDict(dirToCheck=os.getcwd()) # For performance, note that there is nothing yet in place to stop us # from rereading a large dir of par files every time this is called return # we've done enough # Do a glob in the given dir flist = glob.glob(dirToCheck + "/*.par") if len(flist) <= 0: return # At this point, we have files. Foreach, figure out the task and # package it is for, and add it's pathname to the dict. for supfname in flist: buf = [] try: with open(supfname, errors="ignore") as supfile: buf = supfile.readlines() except OSError: pass if len(buf) < 1: warning("Unable to read special use parameter file: " + supfname, level=-1) continue # get task and pkg names, and verify this is a correct file tupKey = None for line in buf: mo = _re_taskmeta.match(line) if mo: # the syntax is right, get the task and pkg names tupKey = (mo.group(1), mo.group(2)) break # only one TASKMETA line per file if tupKey: if tupKey in _specialUseParFileDict: supflist = _specialUseParFileDict[tupKey] if supfname not in supflist: _specialUseParFileDict[tupKey].append(supfname) else: _specialUseParFileDict[tupKey] = [ supfname, ] # If it does not have the TASKMETA line, then it is likely a regular # IRAF .par file. How it got here we don't know, but it got dropped # here somehow and warning the user continuously about this would be # very annoying, so be quiet about it. def newSpecialParFile(taskName, pkgName, pathName): """ Someone has just created a new one and we are being notified of that fact so that we can update the dict. """ # We could at this point simply re-scan the disk for files, but for # now let's assume the user doesnt want that. Just add this entry to # the dict. Someday, after we gauge usage, we could change this to # re-scan and add this entry to the dict if not there yet. global _specialUseParFileDict # lazy init - only search disk here when abs. necessary if _specialUseParFileDict is None: _updateSpecialParFileDict() tupKey = (taskName, pkgName) if tupKey in _specialUseParFileDict: if pathName not in _specialUseParFileDict[tupKey]: _specialUseParFileDict[tupKey].append(pathName) else: _specialUseParFileDict[tupKey] = [ pathName, ] def haveSpecialVersions(taskName, pkgName): """ This is a simple check to see if special-purpose parameter files have been found for the given task/package. This returns True or False. If the dictionary has not been created yet, this initializes it. Note that this may take some time reading the disk. """ global _specialUseParFileDict # Always update the _specialUseParFileDict, since we may have changed # directories into a new work area with as-yet-unseen .par files _updateSpecialParFileDict() # Check and return answer tupKey = (taskName, pkgName) return tupKey in _specialUseParFileDict def getSpecialVersionFiles(taskName, pkgName): """ Returns a (possibly empty) list of path names for special versions of parameter files. This also causes lazy initialization.""" global _specialUseParFileDict tupKey = (taskName, pkgName) if haveSpecialVersions(taskName, pkgName): return _specialUseParFileDict[tupKey] else: return [] # ----------------------------------------------------- # Read IRAF .par file and return list of parameters # ----------------------------------------------------- # Parameter file is basically comma-separated fields, but # with some messy variations involving embedded quotes # and the ability to break the final field across lines. # First define regular expressions used in parsing # Patterns that match a quoted string with embedded \" or \' # From Freidl, Mastering Regular Expressions, p. 176. # # Modifications: # - I'm using the "non-capturing" parentheses (?:...) where # possible; I only capture the part of the string between # the quotes. # - Match leading white space and optional trailing comma. # - Pick up any non-whitespace between the closing quote and # the comma or end-of-line (which is a syntax error.) # Any matched string gets captured into djunk or sjunk # variable, depending on which quotes were matched. whitespace = r'[ \t]*' optcomma = r',?' noncommajunk = r'[^,]*' double = whitespace + r'"(?P<double>[^"\\]*(?:\\.[^"\\]*)*)"' + \ whitespace + r'(?P<djunk>[^,]*)' + optcomma single = whitespace + r"'(?P<single>[^'\\]*(?:\\.[^'\\]*)*)'" + \ whitespace + r'(?P<sjunk>[^,]*)' + optcomma # Comma-terminated string that doesn't start with quote # Match explanation: # - match leading white space # - if end-of-string then done with capture # - elif lookahead == comma then done with capture # - else match not-[comma | blank | quote] followed # by string of non-commas; then done with capture # - match trailing comma if present # # Trailing blanks do get captured (which I think is # the right thing to do) comma = whitespace + r"(?P<comma>$|(?=,)|(?:[^, \t'" + r'"][^,]*))' + optcomma # Combined pattern field = '(?:' + comma + ')|(?:' + double + ')|(?:' + single + ')' _re_field = re.compile(field, re.DOTALL) # Pattern that matches trailing backslashes at end of line _re_bstrail = re.compile(r'\\*$') # clean up unnecessary global variables del whitespace, field, comma, optcomma, noncommajunk, double, single def _readpar(filename, strict=0): """Read IRAF .par file and return list of parameters""" global _re_field, _re_bstrail param_dict = {} param_list = [] with open(os.path.expanduser(filename), errors="ignore") as fh: lines = fh.readlines() # reverse order of lines so we can use pop method lines.reverse() while lines: # strip whitespace (including newline) off both ends line = lines.pop().strip() # skip comments and blank lines # "..." is weird line that occurs in cl.par if len(line) > 0 and line[0] != '#' and line != "...": # Append next line if this line ends with continuation character. while line[-1:] == "\\": # odd number of trailing backslashes means this is continuation if (len(_re_bstrail.search(line).group()) % 2 == 1): try: line = line[:-1] + lines.pop().rstrip() except IndexError: raise SyntaxError(filename + ": Continuation on last line\n" + line) else: break flist = [] i1 = 0 while len(line) > i1: mm = _re_field.match(line, i1) if mm is None: # Failure occurs only for unmatched leading quote. # Append more lines to get quotes to match. (Probably # want to restrict this behavior to only the prompt # field.) while mm is None: try: nline = lines.pop() except IndexError: # serious error, run-on quote consumed entire file sline = line.split('\n') raise SyntaxError(filename + ": Unmatched quote\n" + sline[0]) line = line + '\n' + nline.rstrip() mm = _re_field.match(line, i1) if mm.group('comma') is not None: g = mm.group('comma') # completely omitted field (,,) if g == "": g = None # check for trailing quote in unquoted string elif g[-1:] == '"' or g[-1:] == "'": warning( filename + "\n" + line + "\n" + "Unquoted string has trailing quote", strict) elif mm.group('double') is not None: if mm.group('djunk'): warning( filename + "\n" + line + "\n" + "Non-blank follows quoted string", strict) g = mm.group('double') elif mm.group('single') is not None: if mm.group('sjunk'): warning( filename + "\n" + line + "\n" + "Non-blank follows quoted string", strict) g = mm.group('single') else: raise SyntaxError( filename + "\n" + line + "\n" + "Huh? mm.groups()=" + repr(mm.groups()) + "\n" + "Bug: doesn't match single, double or comma??") flist.append(g) # move match pointer i1 = mm.end() try: par = IrafParFactory(flist, strict=strict) except KeyboardInterrupt: raise except Exception as exc: # XXX Shouldn't catch all exceptions here -- this could # XXX screw things up if Verbose: import traceback traceback.print_exc() raise SyntaxError(filename + "\n" + line + "\n" + str(flist) + "\n" + str(exc)) if par.name in param_dict: warning( filename + "\n" + line + "\n" + "Duplicate parameter " + par.name, strict) else: param_dict[par.name] = par param_list.append(par) return param_list ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/iraftask.py�����������������������������������������������������������������������0000644�0001750�0001750�00000224352�14214070451�014670� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""module iraftask.py -- defines IrafTask and IrafPkg classes R. White, 2000 June 26 iraftask defines the original PyRAF task functionality which pre-dates the creation of IRAF ECL. irafecl is closely related and derived from iraftask, providing drop-in replacements for the Task classes defined here which also support ECL syntax like "iferr" and $errno. """ import fnmatch import os import sys import copy import re from .tools import basicpar, irafglobals from .tools.irafglobals import IrafError, Verbose from . import subproc from . import irafinst from . import irafpar from . import irafexecute from . import cl2py from . import iraf from .tools import minmatch, irafutils, taskpars # may be set to function to monitor task execution # function gets called for every task execution executionMonitor = None # ----------------------------------------------------- # IRAF task class # ----------------------------------------------------- # basic IrafTask attributes _IrafTask_attr_dict = { '_name': None, '_pkgname': None, '_pkgbinary': None, '_hidden': 0, '_hasparfile': 1, '_tbflag': 0, # full path names and parameter list get filled in on demand '_fullpath': None, # parameters have a current set of values and a default set '_currentParList': None, '_defaultParList': None, '_runningParList': None, '_currentParpath': None, '_defaultParpath': None, '_scrunchParpath': None, '_parDictList': None, '_foreign': 0, } # This is a list of all the IrafTask objects that have been created. # There are variables in iraffunctions that superficially # resemble this, but none of them contain the complete list, so I # keep it here. This is currently used only for the "taskinfo" # operation. # # We generally approach this list interactively and with a wildcards. # I don't see any particularly useful indexing, so it is just a plain # linear search. # all_task_definitions = [] # use empty "tag" class from irafglobals as base class class IrafTask(irafglobals.IrafTask, taskpars.TaskPars): """IRAF task class""" # We remember parameters to the __init__ function # # prefix # ? - see obj._saved_prefix # # name # the name of the task. We don't actually save this, but # obj._name contains the name as we may have modified it. # # suffix # ? - see obj._saved_suffix # # filename # ? - obviously more than just a file name. obj._filename # is the modified value # # pkgname # the name of the package that this task is in. see obj._pkgname # # pkgbinary # places that the binary may be. see obj._pkgbinary # def __init__(self, prefix, name, suffix, filename, pkgname, pkgbinary): # for this heavily used code, pull out the dictionary and # initialize it directly to avoid calls to __setattr__ # # b.t.w. do not try to set the attributes directly - it doesn't # work. (not sure if that is a bug or a "feature") objdict = self.__dict__ # remember the task definition in case we want to see it later all_task_definitions.append(self) objdict['_saved_suffix'] = suffix objdict['_saved_prefix'] = prefix # stuff all the parameters into the object objdict.update(_IrafTask_attr_dict) sname = name.replace('.', '_') if sname != name: print("Warning: '.' illegal in task name, changing", name, "to", sname) spkgname = pkgname.replace('.', '_') if spkgname != pkgname: print("Warning: '.' illegal in pkgname, changing", pkgname, "to", spkgname) objdict['_name'] = sname objdict['_pkgname'] = spkgname objdict['_pkgbinary'] = [] self.addPkgbinary(pkgbinary) # tasks with names starting with '_' are implicitly hidden if name[0:1] == '_': objdict['_hidden'] = 1 if prefix == '$': objdict['_hasparfile'] = 0 if suffix == '.tb': objdict['_tbflag'] = 1 if filename and filename[0] == '$': # this is a foreign task objdict['_foreign'] = 1 objdict['_filename'] = filename[1:] # handle weird syntax for names if self._filename == 'foreign': objdict['_filename'] = name elif self._filename[:8] == 'foreign ': objdict['_filename'] = name + self._filename[7:] elif filename[:2] == '$0': objdict['_filename'] = name + filename[2:] else: objdict['_filename'] = filename def initTask(self, force=0): """Fill in full pathnames of files and read parameter file(s) Force indicates whether shortcut initialization can be used or not. (No difference for base IrafTask.) """ if self._filename and not self._fullpath: if irafinst.EXISTS: self._initFullpath() # allow to throw on error else: # be more accommodating try: self._initFullpath() except IrafError: self._initNoIrafTask() if self._currentParList is None: self._initParpath() self._initParList() def _initNoIrafTask(self): """ Special-case handle the initialization that is going awry due to a missing IRAF installation. """ # Handle non-IRAF installs - could not find the file anywhere # Handle .par files differently from other types orig = self._filename base = os.path.basename(self._filename) base = base[1 + base.rfind('$'):] self._filename = f'{irafinst.NO_IRAF_PFX}/{base}' if Verbose > 1: print('Task "' + self._name + '" needed "' + orig + '" got: ' + self._filename) # ========================================================= # public accessor methods for attributes # ========================================================= # --------------------------------------------------------- # first set returns current values (which may be None if # initTask has not been executed yet) # --------------------------------------------------------- def getName(self): return self._name def getPkgname(self): return self._pkgname def getPkgbinary(self): return self._pkgbinary def isHidden(self): return self._hidden def hasParfile(self): return self._hasparfile def getTbflag(self): return self._tbflag def getForeign(self): return self._foreign def getFilename(self): return self._filename # --------------------------------------------------------- # second set initializes task variables (which were deferred to # speed up initial instance creation) # --------------------------------------------------------- def getFullpath(self): """Return full path name of executable""" self.initTask() return self._fullpath def getParpath(self): """Return full path name of parameter file""" self.initTask() return self._currentParpath def getParList(self, docopy=0): """Return list of all parameter objects""" self.initTask(force=1) plist = self._runningParList or self._currentParList if plist: return plist.getParList(docopy=docopy) else: return [] def getDefaultParList(self): """Return default list of all parameter objects""" self.initTask(force=1) plist = self._defaultParList if plist: return plist.getParList() else: return [] def getParDict(self): """Return (min-match) dictionary of all parameter objects""" self.initTask(force=1) plist = self._runningParList or self._currentParList if plist: return plist.getParDict() else: return minmatch.MinMatchDict() def getParObject(self, paramname, exact=0, alldict=0): """Get the IrafPar object for a parameter If exact is set, param name must match exactly. If alldict is set, look in all dictionaries (default is just this task's dictionaries.) """ self.initTask() # search the standard dictionaries for the parameter # most of the time it will be in the active task dictionary try: paramdict = self.getParDict() if paramdict._has(paramname, exact=exact): return paramdict[paramname] except minmatch.AmbiguousKeyError as e: # re-raise the error with a bit more info raise IrafError(f"Cannot get parameter `{paramname}'\n{str(e)}") if alldict: # OK, the easy case didn't work -- now initialize the # complete parDictList (if necessary) and search them all if self._parDictList is None: self._setParDictList() for dictname, paramdict in self._parDictList: if paramdict._has(paramname, exact=exact): return paramdict[paramname] raise IrafError("Unknown parameter requested: " + paramname) def getAllMatches(self, param): """Return list of names of all parameters that may match param""" self.initTask(force=1) plist = self._runningParList or self._currentParList if plist: return plist.getAllMatches(param) else: return [] # --------------------------------------------------------- # modify and test attributes # --------------------------------------------------------- def addPkgbinary(self, pkgbinary): """Add another entry in list of possible package binary locations Parameter can be a string or a list of strings""" if not pkgbinary: return elif isinstance(pkgbinary, str): if pkgbinary and (pkgbinary not in self._pkgbinary): self._pkgbinary.append(pkgbinary) else: for pbin in pkgbinary: if pbin and (pbin not in self._pkgbinary): self._pkgbinary.append(pbin) def setHidden(self, value=1): """set hidden attribute, which can be specified in a separate 'hide' statement """ self._hidden = value def isConsistent(self, other): """Returns true if this task is consistent with another task object""" return self.__class__ == other.__class__ and \ self.getFilename() == other.getFilename() and \ self.hasParfile() == other.hasParfile() and \ self.getForeign() == other.getForeign() and \ self.getTbflag() == other.getTbflag() # --------------------------------------------------------- # run the task # --------------------------------------------------------- def run(self, *args, **kw): """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 = iraf.redirProcess(kw) # Set parameters ... # The setParList call sets _runningParList, which is a copy that is # only intended to live for the lifetime of the running task. The # _currentParList version lives longer - it represents the on-disk # copy of the par list, during the life of this PyRAF session. kw['_setMode'] = 1 self.setParList(*args, **kw) if Verbose > 1: print(f"run {self._name} ({self.__class__.__name__}: " f"{self._fullpath})", file=sys.stderr) 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) try: # Hook for execution monitor if executionMonitor: executionMonitor(self) self._run(redirKW, specialKW) self._updateParList(save) if Verbose > 1: print('Successful task termination', file=sys.stderr) finally: rv = self._resetRedir(resetList, closeFHList) self._deleteRunningParList() if self._parDictList: self._parDictList[0] = (self._name, self.getParDict()) if executionMonitor: executionMonitor() return rv def getMode(self, parList=None): """Returns mode string for this task Searches up the task, package, cl hierarchy for automatic modes """ if parList is not None: mode = parList.getValue('mode', prompt=0) else: pdict = self.getParDict() if pdict: mode = pdict['mode'].get(prompt=0) else: mode = "a" if mode[:1] != "a": return mode # cl is the court of last resort, don't look at its packages if self is iraf.cl: return "h" # package name is undefined only at very start of initialization # just use the standard default if not self._pkgname: return "ql" # up we go -- look in parent package pkg = iraf.getPkg(self._pkgname) # clpackage is at top and is its own parent if pkg is not self: return pkg.getMode() # didn't find it in the package hierarchy, so use cl mode mode = iraf.cl.mode # default is hidden if automatic all the way to top if mode[:1] == "a": return "h" else: return mode def setParList(self, *args, **kw): """Set arguments to task in _runningParList copy of par list Creates a copy of the task parameter list and sets the parameters. It is up to subsequent code (in the run method) to propagate these changes to the persistent parameter list. Special arguments: _setMode=1 to set modes of automatic parameters ParList can be used to pass in an entire parameter list object """ self.initTask(force=1) if self._currentParList is None: return None # Special ParList parameter is used to pass in an entire # parameter list if 'ParList' in kw: parList = kw['ParList'] del kw['ParList'] if isinstance(parList, str): # must be a .par filename filename = parList parList = irafpar.IrafParList(self.getName(), filename) elif parList and not isinstance(parList, irafpar.IrafParList): raise TypeError("ParList parameter must be a filename or " "an IrafParList object") else: parList = None if self._runningParList is not None: # only one runningParList at a time -- all tasks use it newParList = self._runningParList parList = None else: if parList: newParList = copy.deepcopy(parList) else: newParList = copy.deepcopy(self._currentParList) if '_setMode' in kw: _setMode = kw['_setMode'] del kw['_setMode'] else: _setMode = 0 # create parlist copies for pset tasks too for p in newParList.getParList(): if isinstance(p, irafpar.IrafParPset): p.get().setParList() # now, finally, set the passed-in parameters newParList.setParList(*args, **kw) if _setMode: # set mode of automatic parameters mode = self.getMode(newParList) for p in newParList.getParList(): p.mode = p.mode.replace("a", mode) if parList: # XXX Set all command-line flags for parameters when a # XXX parlist is supplied so that it does not prompt for # XXX missing parameters. Is this the preferred behavior? newParList.setAllFlags() self._runningParList = newParList # --------------------------------------------------------- # task parameter access # --------------------------------------------------------- def setParam(self, qualifiedName, newvalue, check=1, exact=0, scope='', idxHint=None): """Set parameter specified by qualifiedName to newvalue. qualifiedName can be a simple parameter name or can be [[package.]task.]paramname[.field]. If check is set to zero, does not check value to make sure it satisfies min-max range or choice list. scope, idxHint are ignored. """ package, task, paramname, pindex, field = _splitName(qualifiedName) # special syntax for package parameters if task == "_": task = self._pkgname if task or package: if not package: # maybe this task is the name of one of the dictionaries? if self._parDictList is None: self._setParDictList() for dictname, paramdict in self._parDictList: if dictname == task: if paramname in paramdict: paramdict[paramname].set(newvalue, index=pindex, field=field, check=check) return else: raise IrafError( "Attempt to set unknown parameter " + qualifiedName + ' for task ' + task) # Not one of our dictionaries, so must find the relevant task if package: task = package + '.' + task try: tobj = iraf.getTask(task) # reattach the index and/or field if pindex: paramname = paramname + '[' + repr(pindex + 1) + ']' if field: paramname = paramname + '.' + field tobj.setParam(paramname, newvalue, check=check) return except KeyError: raise IrafError("Could not find task " + task + " to get parameter " + qualifiedName) except IrafError as e: raise IrafError( str(e) + "\nFailed to set parameter " + qualifiedName) # no task specified, just search the standard dictionaries # most of the time it will be in the active task dictionary paramdict = self.getParDict() if paramdict._has(paramname, exact=exact): paramdict[paramname].set(newvalue, index=pindex, field=field, check=check) return # OK, the easy case didn't work -- now initialize the # complete parDictList (if necessary) and search them all if self._parDictList is None: self._setParDictList() for dictname, paramdict in self._parDictList: if paramdict._has(paramname, exact=exact): paramdict[paramname].set(newvalue, index=pindex, field=field, check=check) return else: raise IrafError("Attempt to set unknown lone parameter " + qualifiedName) def getParam(self, qualifiedName, native=1, mode=None, exact=0, prompt=1): """Return parameter specified by qualifiedName. qualifiedName can be a simple parameter name or can be [[package.]task.]paramname[.field]. Paramname can also have an optional subscript, "param[1]". If native is non-zero (default), returns native format (e.g. float for floating point parameter.), otherwise returns string value. If exact is set, parameter name must match exactly. Default is to do minimum match. If prompt is 0, does not prompt for parameter value (even if parameter is undefined.) """ # DBG "GP:", str(self._name), qualifiedName, native, mode, exact, prompt package, task, paramname, pindex, field = _splitName(qualifiedName) if (not task) or (task == self._name): # no task specified, just search the standard dictionaries return self._getParValue(paramname, pindex, field, native, mode, exact=exact, prompt=prompt) # when task is specified, ignore exact flag -- always do minmatch # special syntax for package parameters if task == "_": task = self._pkgname if not package: # maybe this task is the name of one of the dictionaries? if self._parDictList is None: self._setParDictList() for dictname, paramdict in self._parDictList: if dictname == task: if paramname in paramdict: return self._getParFromDict(paramdict, paramname, pindex, field, native, mode="h", prompt=prompt) else: raise IrafError("Unknown parameter requested: " + qualifiedName) # Not one of our dictionaries, so must find the relevant task if package: task = package + '.' + task try: tobj = iraf.getTask(task) return tobj._getParValue(paramname, pindex, field, native, mode="h", prompt=prompt) except KeyError: raise IrafError("Could not find task " + task + " to get parameter " + qualifiedName) except IrafError as e: raise IrafError( str(e) + "\nFailed to get parameter " + qualifiedName) def _getParValue(self, paramname, pindex, field, native, mode, exact=0, prompt=1): # search the standard dictionaries for the parameter # most of the time it will be in the active task dictionary paramdict = self.getParDict() try: if paramdict._has(paramname, exact=exact): return self._getParFromDict(paramdict, paramname, pindex, field, native, mode=mode, prompt=prompt) except minmatch.AmbiguousKeyError as e: # re-raise the error with a bit more info raise IrafError(f"Cannot get parameter `{paramname}'\n{str(e)}") # OK, the easy case didn't work -- now initialize the # complete parDictList (if necessary) and search them all if self._parDictList is None: self._setParDictList() for dictname, paramdict in self._parDictList: if paramdict._has(paramname, exact=exact): return self._getParFromDict(paramdict, paramname, pindex, field, native, mode="h", prompt=prompt) else: raise IrafError(f'Unknown parameter requested: "{paramname}" ' f'for task: "{self._name}" ' f'in pkg: "{self._pkgname}"') # --------------------------------------------------------- # task parameter utility methods # --------------------------------------------------------- def lParam(self, verbose=0): """List the task parameters""" self.initTask(force=1) plist = self._runningParList or self._currentParList if plist: plist.lParam(verbose=verbose) else: sys.stderr.write(f"Task {self._name} has no parameter file\n") sys.stderr.flush() def eParam(self): """Edit the task parameters, PyRAF Tk style""" self.initTask(force=1) # XXX always runs on current par list, not running par list? if self._currentParList: from . import epar epar.epar(self) else: sys.stderr.write(f"Task {self._name} has no parameter file\n") sys.stderr.flush() def tParam(self): """Edit the task parameters, IRAF curses style""" self.initTask(force=1) # XXX always runs on current par list, not running par list? if self._currentParList: from . import tpar tpar.tpar(self) else: sys.stderr.write(f"Task {self._name} has no parameter file\n") sys.stderr.flush() def dParam(self, cl=1): """Dump the task parameters Default is to write CL version of code; if cl parameter is false, writes Python executable code instead. """ self.initTask(force=1) plist = self._runningParList or self._currentParList if plist: if cl: taskname = self._name else: taskname = f"iraf.{self._name}" plist.dParam(taskname, cl=cl) else: sys.stderr.write(f"Task {self._name} has no parameter file\n") sys.stderr.flush() def saveParList(self, filename=None, comment=None): """Write task parameters in .par format to filename (name or handle) If filename is omitted, writes to uparm scrunch file (if possible) Returns a string with the results. """ self.initTask() # XXX always runs on current par list, not running par list? if not self._currentParList: return f"No parameters to save for task {self._name}" if filename is None: if self._scrunchParpath: filename = self._scrunchParpath else: status = f"Unable to save parameters for task {self._name}" if Verbose > 0: print(status, file=sys.stderr) return status rv = self._currentParList.saveParList(filename, comment) return rv def unlearn(self): """Reset task parameters to their default values""" self.initTask(force=1) # XXX always runs on current par list, not running par list? if not self._currentParList: return if self._defaultParList is not None: # update defaultParList from file if necessary self._defaultParList.Update() if self._scrunchParpath and \ (self._scrunchParpath == self._currentParpath): try: os.remove(iraf.Expand(self._scrunchParpath, noerror=1)) except OSError: pass self._currentParList = copy.deepcopy(self._defaultParList) self._currentParpath = self._defaultParpath else: raise IrafError("Cannot find default .par file for task " + self._name) def scrunchName(self): """Return scrunched version of filename (used for uparm files) Scrunched version of filename is chars 1,2,last from package name and chars 1-5,last from task name. """ s = self._pkgname[0:2] if len(self._pkgname) > 2: s = s + self._pkgname[-1:] s = s + self._name[0:5] if len(self._name) > 5: s = s + self._name[-1:] return s # ========================================================= # special methods to give desired object syntax # ========================================================= # parameters are accessible as attributes def __getattr__(self, name): if name[:1] == '_': raise AttributeError(name) self.initTask() try: return self.getParam(name, native=1) except SyntaxError as e: raise AttributeError(str(e)) def __setattr__(self, name, value): # hidden Python parameters go into the standard dictionary # (hope there are none of these in IRAF tasks) if name[:1] == '_': self.__dict__[name] = value elif self.is_pseudo(name): self.__dict__[name] = value else: self.initTask() self.setParam(name, value) def is_pseudo(self, paramname): """Hook enabling ECL pseudos... always returns False""" return False # allow running task using taskname() or with # parameters as arguments, including keyword=value form. def __call__(self, *args, **kw): return self.run(*args, **kw) def __repr__(self): s = (f'<{self.__class__.__name__} {self._name} ({self._filename}) ' f'Pkg: {self._pkgname} Bin: {":".join(self._pkgbinary)}') if self._foreign: s = s + ' Foreign' if self._hidden: s = s + ' Hidden' if self._hasparfile == 0: s = s + ' No parfile' if self._tbflag: s = s + ' .tb' return s + '>' def __str__(self): return repr(self) # ========================================================= # private methods -- may be used by subclasses, but should # not be needed outside this module # ========================================================= def _specialKW(self, kw): """Return dictionary of any special keywords (subclass hook)""" return {} def _applyRedir(self, redirKW): """Apply I/O redirection (irafexecute does this for executables) Return a list of redirections that need to be restored when done. """ return [] def _resetRedir(self, resetList, closeFHList): """Restore redirected I/O and close files""" return iraf.redirReset(resetList, closeFHList) def _run(self, redirKW, specialKW): """Execute task after parameters, I/O redirection are prepared. The implementation of this can differ for each type of task. """ try: irafexecute.IrafExecute(self, iraf.getVarDict(), **redirKW) except irafexecute.IrafProcessError as value: raise IrafError("Error running IRAF task " + self._name + "\n" + str(value)) def _updateParList(self, save=0): """Update parameter list after successful task completion Updates parameter save file if any parameters change. If save flag is set, all changes are saved; if save flag is false, only explicit parameter changes requested by the task are saved. """ if not (self._currentParList and self._runningParList): return newParList = self._runningParList self._runningParList = None mode = self.getMode(newParList) changed = 0 for par in newParList.getParList(): if par.name != "$nargs" and (par.isChanged() or (save and par.isCmdline() and par.isLearned(mode))): changed = 1 # get task parameter object tpar = self._currentParList.getParObject(par.name) # set its value -- don't bother with type checks since # the new and old parameters must be identical tpar.value = par.value # propagate other mutable fields too # don't propagate modes since I changed them # (note IRAF does not propagate prompt, which I consider a bug) tpar.min = par.min tpar.max = par.max tpar.choice = par.choice tpar.prompt = par.prompt tpar.setChanged() if isinstance(par, irafpar.IrafParPset): par.get()._updateParList(save) # save to disk if there were changes if changed: rv = self.saveParList() if Verbose > 1: print(rv, file=sys.stderr) def _deleteRunningParList(self): """Delete the _runningParList parameter list for this and psets""" if self._currentParList and self._runningParList: newParList = self._runningParList self._runningParList = None for par in newParList.getParList(): if isinstance(par, irafpar.IrafParPset): par.get()._deleteRunningParList() def _setParDictList(self): """Set the list of (up to 3) parameter dictionaries for task execution. Parameter dictionaries for execution consist of this task's parameters (which includes any psets referenced), all the parameters for the task of the package loaded for the current task, and the cl parameters. Each dictionary has an associated name (because parameters could be asked for as task.parname as well as just parname). Create this list anew for each execution in case the list of loaded packages has changed. It is stored as an attribute of this object so it can be accessed by the getParam() and setParam() methods. """ # Start with the parameters for the current task self.initTask() parDictList = [(self._name, self.getParDict())] # Next, parameters from the package to which the current task belongs # [Ticket 59: mimic behavior of param.c:lookup_param()] task = iraf.getTask(self.getPkgname()) pd = task.getParDict() if pd: # do not include null dictionaries parDictList.append((self.getPkgname(), pd)) # Lastly, cl parameters cl = iraf.cl if cl is not None: parDictList.append((cl.getName(), cl.getParDict())) # Done self._parDictList = parDictList def _getParFromDict(self, paramdict, paramname, pindex, field, native, mode, prompt): # helper method for getting parameter value (with indirection) # once we find a dictionary that contains it par = paramdict[paramname] pmode = par.mode[:1] if pmode == "a": pmode = mode or self.getMode() v = par.get(index=pindex, field=field, native=native, mode=pmode, prompt=prompt) if isinstance(v, str) and v[:1] == ")": # parameter indirection: call getParam recursively # I'm making the assumption that indirection in a # field (e.g. in the min or max value) is allowed # and that it uses exactly the same syntax as # the argument to getParam, i.e. ')task.param' # refers to the p_value of the parameter, # ')task.param.p_min' refers to the min or # choice string, etc. return self.getParam(v[1:], native=native, mode="h", prompt=prompt) else: return v def _initFullpath(self): """Fill in full pathname of executable""" # This follows the search strategy used by findexe in # cl/exec.c: first it checks in the BIN directory for the # "installed" version of the executable, and if that is not # found it tries the pathname given in the TASK declaration. # Expand iraf variables. We will try both paths if the expand fails. try: exename1 = iraf.Expand(self._filename) # get name of executable file without path basedir, basename = os.path.split(exename1) except IrafError as e: if Verbose > 0: print("Error searching for executable for: " + self._name, file=sys.stderr) print(str(e), file=sys.stderr) exename1 = "" # make our best guess that the basename is what follows the # last '$' in _filename s = self._filename.split("$") basename = s[-1] if basename == "": self._fullpath = "" raise IrafError(f"No filename in task {self._name} definition: " f"`{self._filename}'") # for foreign tasks, just set path to filename (XXX will # want to improve this by checking os path for existence) if self._foreign: self._fullpath = self._filename else: # first look in the task binary directories exelist = [] for pbin in self._pkgbinary: # e.g. ['bin$'] try: exelist.append(iraf.Expand(pbin + basename)) except IrafError as e: if Verbose > 0: print("Error finding executable for: " + self._name, file=sys.stderr) print(str(e), file=sys.stderr) for exename2 in exelist: if os.path.exists(exename2): self._fullpath = exename2 break else: if os.path.exists(exename1): self._fullpath = exename1 else: self._fullpath = "" exelist.append(exename1) raise IrafError( f"Cannot find executable for task {self._name}\n" f"Tried " + ", ".join(exelist)) def _initParpath(self): """Initialize parameter file paths""" if not self._filename: # if filename is missing we won't be able to find parameter file # set hasparfile flag to zero if that is OK self._noParFile() self._hasparfile = 0 if not self._hasparfile: # no parameter file self._defaultParpath = "" self._currentParpath = "" self._scrunchParpath = "" return try: exename1 = iraf.Expand(self._filename) basedir, basename = os.path.split(exename1) if basedir == "": basedir = "." except IrafError as e: if Verbose > 0: print("Error expanding executable name for task " + self._name + ", tried: " + self._filename, file=sys.stderr) print(str(e), file=sys.stderr) exename1 = "" basedir = "" # default parameters are found with task self._defaultParpath = os.path.join(basedir, self._name + ".par") if not os.path.exists(iraf.Expand(self._defaultParpath, noerror=1)): self._noParFile() self._defaultParpath = "" # uparm has scrunched version of par filename with saved parameters # (also handle if they forgot the end-slash on the uparm var) self._scrunchParpath = "uparm$/" + self.scrunchName() + ".par" def _noParFile(self): """Decide what to do if .par file is not found""" # Here I raise an exception, but subclasses (e.g., CL tasks) # can do something different. raise IrafError("Cannot find .par file for task " + self._name + ", tried: " + self._defaultParpath + ", for file: " + self._filename) def _initParList(self): """Initialize parameter list by reading parameter file""" if not self._hasparfile: return self._defaultParList = irafpar.IrafParList( self._name, iraf.Expand(self._defaultParpath, noerror=1)) codePath = 'a' if self._scrunchParpath and os.path.exists( iraf.Expand(self._scrunchParpath, noerror=1)): self._currentParpath = self._scrunchParpath self._currentParList = irafpar.IrafParList( self._name, iraf.Expand(self._currentParpath, noerror=1)) # are lists consistent? if not self._isConsistentPar(): sys.stderr.write("uparm parameter list " f"`{self._currentParpath}' inconsistent with " "default parameters for " f"{self.__class__.__name__} `{self._name}'\n") sys.stderr.flush() # XXX just toss it for now -- later can try to merge new,old try: os.remove(iraf.Expand(self._scrunchParpath, noerror=1)) except OSError: pass self._currentParpath = self._defaultParpath self._currentParList = copy.deepcopy(self._defaultParList) else: self._currentParpath = self._defaultParpath self._currentParList = copy.deepcopy(self._defaultParList) codePath = 'b' assert self._defaultParList._dlen() == \ self._currentParList._dlen(), "Bad deep copy? "+ \ str(self._defaultParList._dlen())+" != "+ \ str(self._currentParList._dlen())+", cpath="+codePath def _isConsistentPar(self): """Check current par list and default par list for consistency""" return (not self._currentParList) or \ self._currentParList.isConsistent(self._defaultParList) # ----------------------------------------------------- # IRAF graphics kernel class # ----------------------------------------------------- class IrafGKITask(IrafTask): """IRAF graphics kernel class (special case of IRAF task)""" def __init__(self, name, filename): """Initialize: only name and executable filename are needed""" IrafTask.__init__(self, '', name, '', filename, 'clpackage', 'bin$') self.setHidden() # all graphics kernel tasks have the same parameters pars = irafpar.IrafParList(name) makepar = irafpar.makeIrafPar pars.addParam(makepar('', datatype='string', name='input', mode='ql')) pars.addParam(makepar('', datatype='string', name='device', mode='h')) pars.addParam(makepar('yes', datatype='bool', name='generic', mode='h')) self._defaultParList = pars self._currentParList = pars def saveParList(self, filename=None): """Never save parameters for kernels""" return "" # ----------------------------------------------------- # IRAF Pset class # ----------------------------------------------------- class IrafPset(IrafTask): """IRAF pset class (special case of IRAF task)""" def __init__(self, prefix, name, suffix, filename, pkgname, pkgbinary): IrafTask.__init__(self, prefix, name, suffix, filename, pkgname, pkgbinary) # check that parameters are consistent with pset: # - not a foreign task # - has a parameter file if self.getForeign(): raise IrafError( f"Bad filename for pset {self.getName()}: {filename}") if not self.hasParfile(): raise KeyError(f"Pset {self.getName()} has no parameter file") def _run(self, redirKW, specialKW): # executing a pset self.eParam() # the cl runs the param editor here; so shall we def __str__(self): # when coerced to a string, pset is name of task # this makes assignment of a pset to a string do the right thing return self.getName() # ----------------------------------------------------- # IRAF Python task class # ----------------------------------------------------- class IrafPythonTask(IrafTask): """IRAF Python task class""" def __init__(self, prefix, name, suffix, filename, pkgname, pkgbinary, function): # filename is the .par file for this task IrafTask.__init__(self, prefix, name, suffix, filename, pkgname, pkgbinary) if self.getForeign(): raise IrafError(f"Python task `{self.getName()}' cannot be foreign" f" (filename=`{filename}')") self.__dict__['_pyFunction'] = function def isConsistent(self, other): """Returns true if this task is consistent with another task object""" return IrafTask.isConsistent(self, other) and \ self._pyFunction == other._pyFunction # ========================================================= # special methods # ========================================================= def __getstate__(self): """Return state for pickling Note that __setstate__ is not needed because returned state is a dictionary """ # Dictionary is OK except for function pointer, which can't # be restored unless function is in the pyraf package if self._pyFunction is None: return self.__dict__ try: module = self._pyFunction.__globals__['__name__'] if module[:6] == 'pyraf.': return self.__dict__ except KeyError: pass # oh well, replace _pyFunction in shallow copy of dictionary sdict = self.__dict__.copy() sdict['_pyFunction'] = None return sdict # ========================================================= # private methods # ========================================================= def _applyRedir(self, redirKW): """Apply I/O redirection""" return iraf.redirApply(redirKW) def _run(self, redirKW, specialKW): """Execute task after parameters, I/O redirection are prepared.""" # extract all parameters parList = self.getParList() pl = [] for par in parList: if par.name not in ['mode', '$nargs']: if isinstance(par, irafpar.IrafParL): # list parameters get passed as objects pl.append(par) elif par.mode == "h" and not par.isLegal(): # illegal hidden value (generally undefined) passed as None pl.append(None) else: # other parameters get passed by value pl.append(par.get(native=1)) # run function on the parameters self._pyFunction(*pl) # ----------------------------------------------------- # parDictList search class (helper for IrafCLTask) # ----------------------------------------------------- class ParDictListSearch: def __init__(self, taskObj): self.__dict__['_taskObj'] = taskObj def __getattr__(self, paramname): if self._taskObj.is_pseudo(paramname): return getattr(self._taskObj, paramname) if paramname[:1] == '_': raise AttributeError(paramname) # try exact match try: return self._taskObj.getParam(paramname, native=1, mode="h", exact=1) except IrafError: pass # try minimum match try: p = self._taskObj.getParObject(paramname, alldict=1) except IrafError as e: # not found at all raise AttributeError(str(e)) # it was found, but we don't allow min-match in CL scripts # print a more useful message raise AttributeError(f"Unknown parameter `{paramname}' " f"(possibly intended `{p.name}'?)") def getParObject(self, paramname): # try exact match try: return self._taskObj.getParObject(paramname, exact=1, alldict=1) except IrafError: pass # try minimum match try: p = self._taskObj.getParObject(paramname, alldict=1) except IrafError as e: # not found at all raise AttributeError(str(e)) # it was found, but we don't allow min-match in CL scripts # print a more useful message raise AttributeError(f"Unknown parameter `{paramname}' " f"(possibly intended `{p.name}'?)") def __setattr__(self, paramname, value): if self._taskObj.is_pseudo(paramname): return setattr(self._taskObj, paramname, value) if paramname[:1] == '_': raise AttributeError(paramname) # try exact match try: return self._taskObj.setParam(paramname, value, exact=1) except IrafError: pass # try minimum match try: p = self._taskObj.getParObject(paramname, alldict=1) except IrafError as e: # not found at all raise AttributeError(str(e)) # it was found, but we don't allow min-match in CL scripts # print a more useful message raise AttributeError(f"Unknown parameter `{paramname}' " f"(possibly intended `{p.name}'?)") # ----------------------------------------------------- # IRAF CL task class # ----------------------------------------------------- class IrafCLTask(IrafTask): """IRAF CL task class""" def __init__(self, prefix, name, suffix, filename, pkgname, pkgbinary): # allow filename to be a filehandle or a filename if isinstance(filename, str): fh = None else: if not hasattr(filename, 'read'): raise TypeError( 'filename must be either a string or a filehandle') fh = filename if hasattr(fh, 'name'): filename = fh.name else: filename = None IrafTask.__init__(self, prefix, name, suffix, filename, pkgname, pkgbinary) if self.getForeign(): raise IrafError(f"CL task `{self.getName()}' cannot be foreign " f"(filename=`{filename}')") # placeholder for Python translation of CL code # (lazy instantiation) self.__dict__['_pycode'] = None self.__dict__['_codeObject'] = None self.__dict__['_clFunction'] = None if fh is not None: # if filehandle was specified, go ahead and do the # initialization now self.initTask(filehandle=fh) # ========================================================= # new public methods for CL task # ========================================================= def getCode(self): """Return a string with the Python code for this task""" self.initTask(force=1) return self._pycode.code def reCompile(self): """Force recompilation of CL code""" if self._pycode is not None: self._pycode.index = None cl2py.codeCache.remove(self) self.initTask(force=1) # ========================================================= # other public methods # ========================================================= def initTask(self, force=0, filehandle=None): """Fill in full pathnames of files, read par file, compile CL code If filehandle is specified, reads CL code from there """ if (not force) and (self._pycode is not None): # quick return if recheck of source code is not forced return if self._filename is None and filehandle is None and \ self._pycode is not None: # another quick return -- if filename and filehandle are # both None and pycode is defined, input must have come # from a filehandle. Then pycode does not need to be # recreated (and in fact, it cannot be recreated.) return if filehandle is not None and self._filename: self._fullpath = iraf.Expand(self._filename) IrafTask.initTask(self) if filehandle is None: filehandle = self._fullpath or self._filename if not cl2py.checkCache(filehandle, self._pycode): # File has changed, force recompilation self._pycode = None if Verbose > 1: print("Cached version out-of-date: " + self._name, file=sys.stderr) if self._pycode is None: # translate code to python if Verbose > 1: print("Compiling CL task ", self._name, id(self), file=sys.stderr) self._codeObject = None self._pycode = cl2py.cl2py(filehandle, parlist=self._defaultParList, parfile=self._defaultParpath) if self._codeObject is None: # No code object, which can happen if function has not # been compiled or if compilation failed. Try compiling # again in any case. self._clFunction = None if self._pkgname: scriptname = f'<CL script {self._pkgname}.{self._name}>' else: # null pkgname -- just use task in name scriptname = f'<CL script {self._name}>' # force compile to inherit future div. so we don't rely on 2.x div. self._codeObject = compile(self._pycode.code, scriptname, 'exec', 0, 0) if self._clFunction is None: # Execute the code to define the Python function in clDict clDict = {} exec(self._codeObject, clDict) self._clFunction = clDict[self._pycode.vars.proc_name] # get parameter list from CL code # This may replace an existing list -- that's OK since # the cl2py code has already checked it for consistency. self._defaultParList = self._pycode.vars.parList # use currentParList from .par file if exists and consistent if self._currentParpath: if not self._defaultParList.isConsistent(self._currentParList): sys.stderr.write( f"uparm parameter list `{self._currentParpath}' " "inconsistent with default parameters for " f"{self.__class__.__name__} `{self._name}'\n") sys.stderr.flush() # XXX just toss it for now -- later can try to merge new,old if self._currentParpath == self._scrunchParpath: try: os.remove(iraf.Expand(self._scrunchParpath, noerror=1)) except OSError: pass self._currentParpath = self._defaultParpath self._currentParList = copy.deepcopy(self._defaultParList) else: self._currentParList = copy.deepcopy(self._pycode.vars.parList) self._currentParpath = self._defaultParpath # ========================================================= # special methods # ========================================================= def __getstate__(self): """Return state for pickling""" # Dictionary is OK except for function pointer # Note that __setstate__ is not needed because # returned state is a dictionary if self._clFunction is None: return self.__dict__ # replace _clFunction in shallow copy of dictionary sdict = self.__dict__.copy() sdict['_clFunction'] = None return sdict # ========================================================= # private methods # ========================================================= def _applyRedir(self, redirKW): """Apply I/O redirection""" return iraf.redirApply(redirKW) def _run(self, redirKW, specialKW): """Execute task after parameters, I/O redirection are prepared.""" self._runCode() def _runCode(self, parList=None, kw={}): """Run the procedure with current parameters""" # add the searchable task object to keywords kw['taskObj'] = ParDictListSearch(self) if parList is None: parList = self.getParList() # XXX # It might be better to pass all parameters as # keywords instead of as positional arguments? # That would be more robust against some errors # but would also not allow certain IRAF-like # behaviors (where the .par file gives a different # name for the parameter.) # XXX self._clFunction(*parList, **kw) def _noParFile(self): """Decide what to do if .par file is not found""" # For CL tasks, it is OK if no .par pass def _isConsistentPar(self): """Check current par list and default par list for consistency""" # they do not have to be consistent for CL task (at least not # where this is called, in IrafTask.initTask). # XXX This is a bit lax, eh? Need something a bit stricter. return 1 def isConsistent(self, other): """Returns true if this task is consistent with another task object""" if self.getFilename() is not None or other.getFilename() is not None: return IrafTask.isConsistent(self, other) else: return self._pycode == other._pycode # ----------------------------------------------------- # IRAF package class # ----------------------------------------------------- # use empty "tag" class from irafglobals as base class class IrafPkg(IrafCLTask, irafglobals.IrafPkg): """IRAF package class (special case of IRAF task)""" def __init__(self, prefix, name, suffix, filename, pkgname, pkgbinary): IrafCLTask.__init__(self, prefix, name, suffix, filename, pkgname, pkgbinary) self._loaded = 0 self._tasks = minmatch.MinMatchDict() self._subtasks = minmatch.MinMatchDict() self._pkgs = minmatch.MinMatchDict() # ========================================================= # new public methods for package # ========================================================= def __str__(self): """ Describe this object. """ retval = "IrafPkg: name="+self.getName()+", pkg="+self.getPkgname()+ \ ", file="+self.getFilename()+"\n" retval += "tasks: " + str(self._tasks) + "\n" retval += "subtasks: " + str(self._subtasks) + "\n" retval += "packages: " + str(self._pkgs) + "\n" return retval def isLoaded(self): """Returns true if this package has already been loaded""" return self._loaded def addTask(self, task, fullname): """Add a task to the task list for this package Just store the name of the task to avoid cycles """ name = task.getName() self._tasks.add(name, fullname) # sub-packages get added to a separate list if isinstance(task, IrafPkg): self._pkgs.add(name, name) # ========================================================= # other public methods # ========================================================= def getAllMatches(self, name, triedpkgs=None): """Return list of names of all parameters/tasks that may match name""" self.initTask(force=1) plist = self._runningParList or self._currentParList if plist: matches = plist.getAllMatches(name) else: matches = [] if self._loaded: # tasks in this package if name == "": matches.extend(list(self._tasks.keys())) else: matches.extend(self._tasks.getallkeys(name, [])) # tasks in subpackages if not triedpkgs: triedpkgs = {} triedpkgs[id(self)] = 1 getPkg = iraf.getPkg getTried = triedpkgs.get for fullname in self._pkgs.values(): p = getPkg(fullname) if p._loaded and (not getTried(id(p))): try: matches.extend( p.getAllMatches(name, triedpkgs=triedpkgs)) except AttributeError: pass return matches def unlearn(self): """Resets parameters for all tasks in the package to their default values""" # If package isn't loaded, just unlearn the top-level package parameters, # otherwise unlearn top-level ones AND all sub tasks. IrafCLTask.unlearn(self) if self._loaded: # Loop over all tasks in the package for task in self._tasks.keys(): iraf.getTask(task).unlearn() def __getattr__(self, name): """Return the task or param 'name' from this package (if it exists).""" if name[:1] == '_': raise AttributeError(name) self.initTask() # return package parameter if it exists plist = self._runningParList or self._currentParList if plist and plist.hasPar(name): return plist.getValue(name, native=1, mode=self.getMode()) # else search for task with the given name if not self._loaded: raise AttributeError("Package " + self.getName() + " has not been loaded; no tasks are defined") fullname = self._getTaskFullname(name) if fullname: return iraf.getTask(fullname) else: raise AttributeError(f"Parameter {name} not found") # ========================================================= # private methods # ========================================================= def _getTaskFullname(self, name, triedpkgs=None): """Return the full name (pkg.task) of task 'name' from this package Returns None if task is not found. Also searches subpackages for the task. triedpkgs is a dictionary with all the packages that have already been tried. It is used to avoid infinite recursion when packages contain themselves. Tasks that are found are added to _tasks dictionary to speed repeated searches. """ if not self._loaded: return None task = self._tasks.get(name) if task: return task # try subpackages task = self._subtasks.get(name) if task: return task # search subpackages if not triedpkgs: triedpkgs = {} triedpkgs[id(self)] = 1 getPkg = iraf.getPkg getTried = triedpkgs.get for fullname in self._pkgs.values(): p = getPkg(fullname) if p._loaded and (not getTried(id(p))): task = p._getTaskFullname(name, triedpkgs=triedpkgs) if task: self._subtasks.add(name, task) return task return None def _specialKW(self, kw): """Handle special _doprint, _hush keywords""" # Special _hush keyword is used to suppress most output when loading # packages. Default is to print output. # Implement by redirecting stdout to /dev/null (but don't override # other redirection requests) if '_hush' in kw: if kw['_hush'] and \ not (('Stdout' in kw) or ('StdoutAppend' in kw)): kw['Stdout'] = '/dev/null' del kw['_hush'] # Special _doprint keyword is used to control whether tasks are listed # after package has been loaded. Default is to list them if cl.menus # is set, or not to list them if it is not set. if '_doprint' in kw: doprint = kw['_doprint'] del kw['_doprint'] else: doprint = iraf.cl.menus return {'doprint': doprint} def _run(self, redirKW, specialKW): """Execute task after parameters, I/O redirection are prepared.""" doprint = specialKW['doprint'] # if already loaded, just add to iraf.loadedPath iraf.loadedPath.append(self) if not self._loaded: self._loaded = 1 iraf.addLoaded(self) if Verbose > 1: print("Loading pkg: " + self.getName() + "(" + self.getFullpath() + ")", file=sys.stderr) menus = iraf.cl.menus try: iraf.cl.menus = 0 self._runCode() # if other packages were loaded, put this on the # loadedPath list one more time if iraf.loadedPath[-1] is not self: iraf.loadedPath.append(self) if doprint: iraf.listTasks(self) finally: iraf.cl.menus = menus # ----------------------------------------------------- # Turn an IrafCLTask into an IrafPkg # This is necessary because sometimes package scripts # are incorrectly defined as simple CL tasks. (Currently # the only example I know of is the imred/ccdred/ccdtest # package, but there could be others.) Need to keep # the same object (because there may be multiple references # to it) but repair the mistake by changing its class. # # A bit scary, but it works (at least in the current version # of Python.) # # This doesn't do everything that might be necessary. E.g., it does # not print the package contents after loading and does not put the # package on the list of loaded pcakges. Leave that up to the calling # routine. # ----------------------------------------------------- def mutateCLTask2Pkg(o, loaded=1, klass=IrafPkg): """Hack an IRAF CL task object into an IRAF package object""" if isinstance(o, IrafPkg): return if not isinstance(o, IrafCLTask): raise TypeError("Cannot turn object `{repr(o)}' into an IrafPkg") # add the extra attributes used in IrafPkg # this is usually called while actually loading the package, so by # default loaded flag is set to true o._loaded = loaded o._tasks = minmatch.MinMatchDict() o._pkgs = minmatch.MinMatchDict() # Presto, you're an IrafPkg! o.__class__ = klass # ----------------------------------------------------- # IRAF foreign task class # ----------------------------------------------------- # regular expressions for parameter substitution _re_foreign_par = re.compile(r'\$' + r'((?P<n>[0-9]+)' + r'|(?P<all>\*)' + r'|(\((?P<paren>[0-9]+)\))' + r'|(\((?P<allparen>\*)\))' + r')') class IrafForeignTask(IrafTask): """IRAF foreign task class""" def __init__(self, prefix, name, suffix, filename, pkgname, pkgbinary): IrafTask.__init__(self, prefix, name, suffix, filename, pkgname, pkgbinary) # check that parameters are consistent with foreign task: # - foreign flag set # - no parameter file if not self.getForeign(): raise IrafError(f"Bad filename for foreign task {self.getName()}: " f"{filename}") if self.hasParfile(): if Verbose > 0: print("Foreign task " + self.getName() + " cannot have a parameter file", file=sys.stderr) self._hasparfile = 0 def setParList(self, *args, **kw): """Set arguments to task Does not use IrafParList structure -- just keeps list of the arguments """ if '_setMode' in kw: del kw['_setMode'] if len(kw) > 0: raise ValueError(f'Illegal keyword parameters {list(kw.keys())} ' f'for task {self._name}') # self._args = args # Insure that all arguments passed to ForeignTasks are # converted to strings, including objects which are not # naturally converted to strings. # self._args = map(re.escape,map(str,args)) self._args = list(map(self._str_escape, args)) # ========================================================= # private methods # ========================================================= def _str_escape(self, arg): if not isinstance(arg, str): _arg = re.escape(str(arg)) else: _arg = arg return _arg def _applyRedir(self, redirKW): """Apply I/O redirection""" return iraf.redirApply(redirKW) def _run(self, redirKW, specialKW): """Execute task after parameters, I/O redirection are prepared.""" args = self._args self._nsub = 0 # create command line cmdline = _re_foreign_par.sub(self._parSub, self._filename) if self._nsub == 0 and args: # no argument substitution, just append all args cmdline = cmdline + ' ' + ' '.join(args) if Verbose > 1: print("Running foreign task: " + cmdline, file=sys.stderr) # create and run the sub-process subproc.subshellRedir(cmdline) def _parSub(self, mo): """Substitute an argument for this match object""" self._nsub = self._nsub + 1 n = mo.group('n') if n is not None: # $n -- simple substitution n = int(n) if n > len(self._args): return '' elif n == 0: return self._name else: return self._args[n - 1] n = mo.group('paren') if n is not None: # $(n) -- expand IRAF virtual filenames n = int(n) if n > len(self._args): return '' elif n == 0: return self._name else: return iraf.Expand(self._args[n - 1]) n = mo.group('all') if n is not None: # $* -- append all arguments return ' '.join(self._args) n = mo.group('allparen') if n is not None: # $(*) -- append all arguments with virtual filenames converted return ' '.join(map(iraf.Expand, self._args)) raise IrafError(f"Cannot handle foreign string `{self._filename}' " f"for task {self._name}") # ----------------------------------------------------- # Utility function to split qualified names into components # ----------------------------------------------------- def _splitName(qualifiedName): """Split qualifiedName into components. qualifiedName looks like [[package.]task.]paramname[subscript][.field], where subscript is an index in brackets. Returns a tuple with (package, task, paramname, subscript, field). IRAF one-based subscript is changed to Python zero-based subscript. """ # name components may have had 'PY' appended if they match Python keywords slist = list(map(irafutils.untranslateName, qualifiedName.split('.'))) # add field=None if not present if len(slist) == 1 or not basicpar.isParField(slist[-1]): # no field slist.append(None) if len(slist) > 4: raise IrafError("Illegal syntax for parameter: " + qualifiedName) slist = [None] * (4 - len(slist)) + slist # parse possible subscript and insert into list paramname = slist[2] pstart = paramname.find('[') if pstart >= 0: try: pend = paramname.rindex(']') pindex = int(paramname[pstart + 1:pend]) - 1 slist[2:3] = [paramname[:pstart], pindex] except (TypeError, ValueError): raise IrafError("Illegal syntax for array parameter: " + qualifiedName) else: slist[3:3] = [None] return slist # # When a user has a problem, I often wonder where the task they are # using came from and how it is defined. This set of tools shows a # hierarchy of the task/package definitions that led us here. # # (I would also like to know what file contained the task definition, but # that information is not available) # # This is used by the task "taskinfo" in iraffunctions.py; there is some # user documentation there. # # find all the task definitions that match a particular wildcard def gettask(name): return [x for x in all_task_definitions if fnmatch.fnmatch(x._name, name)] # make a printable line about a single task object - this doesn't say everything, # but it may say enough without being too cluttered. def printable_task_def(x): cl = str(x.__class__) if '.' in cl: cl = cl.split('.')[-1] # I wonder if there is a significance to using getFullpath instead of _filename s = f"{x._name} : {x.getFullpath()} - pkgbinary={x._pkgbinary} class={cl}" return s # show a task line, then show the package that task may have come from, then where # that came from, and so on. The top level is normally "clpackage" which is its # own parent. def showtasklong(name, level=0): l = gettask(name) if len(l) < 1: print((" " * level) + f'{name} : NOT FOUND') else: l.sort() for x in l: print((" " * level) + printable_task_def(x)) next_name = x._pkgname if (next_name == name) or (level > 15): pass else: showtasklong(next_name, level=level + 1) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/irafukey.py�����������������������������������������������������������������������0000644�0001750�0001750�00000004722�14214070451�014700� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" implement IRAF ukey functionality """ import os import sys import termios from . import wutil from .tools import capable, irafutils # This class emulates the IRAF ukey parameter mechanism. IRAF calls for # a ukey parameter and expects that the user will type a character in # response. The value of this character is then returned to the iraf task def getSingleTTYChar(): # return type str in all Python versions """Returns None if Control-C is typed or any other exception occurs""" # Ripped off from python FAQ fd = sys.stdin.fileno() old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~(termios.ICANON | termios.ECHO | termios.ISIG) new[6][termios.VMIN] = 1 new[6][termios.VTIME] = 0 termios.tcsetattr(fd, termios.TCSANOW, new) c = None try: # allow Tk mainloop to run while waiting... # vanilla version would be c = os.read(fd, 1) if capable.OF_GRAPHICS: c = irafutils.tkread(fd, 1) else: c = os.read(fd, 1).decode(errors='replace') finally: termios.tcsetattr(fd, termios.TCSAFLUSH, old) return c def ukey(): """Returns the string expected for the IRAF ukey parameter""" # set focus to terminal if it is not already there wutil.focusController.setFocusTo('terminal') char = getSingleTTYChar() if not char: # on control-C, raise KeyboardInterrupt raise KeyboardInterrupt() elif char == '\004': # on control-D, raise EOF raise EOFError() elif ord(char) <= ord(' '): # convert to octal ascii representation returnStr = f'\\{ord(char):03o}' elif char == ':': # suck in colon string until newline is encountered done = 0 sys.stdout.write(':') sys.stdout.flush() colonString = '' while not done: char = getSingleTTYChar() if (not char) or (char == '\n'): done = 1 elif char == '\b': # backspace colonString = colonString[:-1] sys.stdout.write('\b \b') sys.stdout.flush() elif ord(char) >= ord(' '): colonString = colonString + char sys.stdout.write(char) sys.stdout.flush() else: # ignore all other characters pass returnStr = ': ' + colonString else: returnStr = char return returnStr ����������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/msgiobuffer.py��������������������������������������������������������������������0000644�0001750�0001750�00000017744�14212324745�015407� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""module 'msgiobuffer.py' -- module containing the MsgIOBuffer class. This class creates a scrolling canvas composed of a message box and an I/O frame. The message box contains the history of I/O messages; the I/O frame contains the latest I/O from the interactive program. M.D. De La Pena, 2000 June 28 """ # System level modules from tkinter import (StringVar, BooleanVar, Canvas, Frame, Scrollbar, Label, Entry, Message, TRUE, FALSE, VERTICAL, NORMAL, DISABLED, FLAT, SUNKEN, RAISED, TOP, LEFT, RIGHT, Y, X, NW, SW, END) class MsgIOBuffer(Frame): """MsgIOBuffer class""" def __init__(self, parent, width=100, viewHeight=None, text=""): """Constructor for the MsgIOBuffer class""" Frame.__init__(self) # Initialize class attributes self.messageText = "" self.currentText = text self.minHgt = 25 # try 65 with Tk8.5 on OSX self.viewHeight = viewHeight self.entrySetting = StringVar() self.entrySetting.set("") self.entryValue = self.entrySetting.get() self.waitFlag = BooleanVar() self.waitFlag.set(TRUE) # Set up the frame to hold the message and the I/O self.parent = parent self.msgIO = Frame(self.parent, bd=2, relief=FLAT, takefocus=FALSE) # Overlay a canvas on the frame self.msgIO.canvas = Canvas(self.msgIO, takefocus=FALSE, highlightthickness=0) # Attach a vertical scrollbar to the canvas self.msgIO.vscroll = Scrollbar(self.msgIO, orient=VERTICAL, width=11, relief=SUNKEN, activerelief=RAISED, takefocus=FALSE) self.msgIO.canvas['yscrollcommand'] = self.msgIO.vscroll.set self.msgIO.vscroll['command'] = self.msgIO.canvas.yview self.msgIO.vscroll.pack(side=RIGHT, fill=Y) # Pack the canvas self.msgIO.canvas.pack(side=LEFT, fill=X, expand=TRUE, padx=4) # Do not pack the frame here. Do it in the application. ### # self.msgIO.pack(side = TOP, fill = X, expand = TRUE) # Define a frame that will sit on the canvas # This frame will hold a message box and a small I/O frame self.msgIO.canvas.f = Frame(self.msgIO.canvas) self.msgIO.canvas.f.pack(fill=X, expand=TRUE) # Generate the window for the canvas self.msgIO.canvas.create_window(0, 0, anchor=NW, window=self.msgIO.canvas.f) # Define a frame for I/O to be placed on the canvas self.msgIO.canvas.f.iomb = Frame(self.msgIO.canvas.f, relief=FLAT, bd=0) # Frame may contain message and a label, or message, label and entry. self.msgIO.canvas.f.iomb.label = Label(self.msgIO.canvas.f.iomb, text=self.currentText, bd=5, takefocus=FALSE) self.msgIO.canvas.f.iomb.entry = Entry(self.msgIO.canvas.f.iomb, highlightthickness=0, bg="#d9d9d9", relief=FLAT, textvariable=self.entrySetting, state=DISABLED, insertwidth=2, takefocus=FALSE) # Bind the carriage return to the entry self.msgIO.canvas.f.iomb.entry.bind('<Return>', self.__getEntryValue) # Define a message box to be placed in the frame self.msgIO.canvas.f.iomb.msg = Message(self.msgIO.canvas.f.iomb, bd=0, relief=FLAT, text=self.messageText, anchor=SW, width=width, takefocus=FALSE) # Pack the widgets in the frame self.msgIO.canvas.f.iomb.msg.pack(side=TOP, fill=X, expand=TRUE) self.msgIO.canvas.f.iomb.label.pack(side=LEFT) self.msgIO.canvas.f.iomb.entry.pack(side=LEFT, fill=X, expand=TRUE) self.msgIO.canvas.f.iomb.pack(side=TOP, fill=X, expand=TRUE) # The full scrolling region is the width of the parent and # the height of the label/entry (25) and the message box (18) # combined. Hardcoded to avoid too much updating which causes # redraws in PyRAF. scrollHgt = 43 self.msgIO.canvas.itemconfigure(1, height=scrollHgt) self.msgIO.canvas.configure(scrollregion=(0, 0, 0, scrollHgt)) # The displayed portion of the window on the canvas is primarily # the label/entry region. if (self.viewHeight is None or self.viewHeight < self.minHgt): self.msgIO.canvas.configure(height=self.minHgt) else: self.msgIO.canvas.configure(height=self.viewHeight) # View is to show the information just moved into the message area self.msgIO.canvas.yview_moveto(1) def updateIO(self, text=""): """Method to update the I/O portion of the scrolling canvas""" # Move the current contents of the I/O frame to the message box self.__updateMsg(self.currentText) # Update the class variable with the latest text self.currentText = text # Now reconfigure the I/O frame self.msgIO.canvas.f.iomb.label.configure(text=text) def readline(self): """Method to set focus to the Entry widget and XXX""" self.__enableEntry() self.msgIO.canvas.f.iomb.entry.wait_variable(self.waitFlag) self.waitFlag.set(TRUE) # Important to have the "\n" on the returned value return (self.entryValue + "\n") def __getEntryValue(self, event=None): """Private method to obtain any value entered in the Entry""" self.entryValue = self.entrySetting.get() self.msgIO.canvas.f.iomb.entry.delete(0, END) # Combine any label value and the entry value in order # to update the current text self.currentText = self.currentText + " " + self.entryValue # Disable the entry self.msgIO.canvas.f.iomb.entry.configure(state=DISABLED) self.waitFlag.set(FALSE) if self.lastFocus: self.lastFocus.focus_set() def __enableEntry(self): """Private method to put the Entry into a normal state for input.""" # Input is requested, so enable the entry box f = self.focus_displayof() if f: self.lastFocus = f.focus_lastfor() else: self.lastFocus = None self.msgIO.canvas.f.iomb.entry.configure(state=NORMAL) self.msgIO.canvas.f.iomb.entry.focus_set() def __updateMsg(self, text=""): """Private method to update the message box of the scrolling canvas.""" # Ensure there is a new line text = "\n" + text # Append the new text to the previous message text self.messageText = self.messageText + text self.msgIO.canvas.f.iomb.msg.configure(text=self.messageText) self.msgIO.canvas.f.update_idletasks() # Reconfigure the canvas size/scrollregion based upon the message box mbHgt = self.msgIO.canvas.f.iomb.msg.winfo_height() scrollHgt = mbHgt + self.minHgt self.msgIO.canvas.itemconfigure(1, height=scrollHgt) self.msgIO.canvas.configure(scrollregion=(0, 0, 0, scrollHgt)) self.msgIO.canvas.yview_moveto(1) ����������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/msgiowidget.py��������������������������������������������������������������������0000644�0001750�0001750�00000021645�14214070451�015406� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" 'msgiowidget.py' -- this is a replacement for the msgiobuffer module. This contains the MsgIOWidget class, which is an optionally hidden scrolling canvas composed of a text widget and frame. When "hidden", it turns into a single-line text widget. """ # System level modules import tkinter # Our modules from .tools import tkrotext def is_USING_X(): """ This is specifically in a function and not at the top of this module so that it is not done until needed. We do not want to casually import wutil anywhere. The import mechanism makes this speedy on the 2nd-Nth attempt anyway. """ from . import wutil return wutil.WUTIL_USING_X class MsgIOWidget(tkinter.Frame): """MsgIOWidget class""" def __init__(self, parent, width=100, text=""): """Constructor""" # We are the main frame that holds everything we do tkinter.Frame.__init__(self, parent) self._parent = parent # Create two sub-frames, one to hold the 1-liner msg I/O, and # the other one to hold the whole scrollable history. self._nowFrame = tkinter.Frame(self, bd=2, relief=tkinter.SUNKEN, takefocus=False) self._histFrame = tkinter.Frame(self, bd=2, relief=tkinter.SUNKEN, takefocus=False) # Put in the expand/collapse button (determine it's sizes) self._expBttnHasTxt = True btxt = '+' if is_USING_X(): px = 2 py = 0 else: # Aqua px = 5 py = 3 if tkinter.TkVersion > 8.4: px = py = 0 btxt = '' self._expBttnHasTxt = False self._expBttn = tkinter.Checkbutton(self._nowFrame, command=self._expand, padx=px, pady=py, text=btxt, indicatoron=0, state=tkinter.DISABLED) self._expBttn.pack(side=tkinter.LEFT, padx=3) # , ipadx=0) # Overlay a label on the frame self._msgLabelVar = tkinter.StringVar() self._msgLabelVar.set(text) self._msgLabelMaxWidth = 65 # 70 works but causes plot redraws when # the history panel is opened/closed self._msgLabel = tkinter.Label(self._nowFrame, textvariable=self._msgLabelVar, anchor=tkinter.W, justify=tkinter.LEFT, width=self._msgLabelMaxWidth, wraplength=width - 100, takefocus=False) self._msgLabel.pack(side=tkinter.LEFT, fill=tkinter.X, expand=False) self._msgLabel.bind('<Double-Button-1>', self._lblDblClk) self._entry = tkinter.Entry(self._nowFrame, state=tkinter.DISABLED, width=1, takefocus=False, relief=tkinter.FLAT, highlightthickness=0) self._entry.pack(side=tkinter.LEFT, fill=tkinter.X, expand=True) self._entry.bind('<Return>', self._enteredText) self._entryTyping = tkinter.BooleanVar() self._entryTyping.set(False) # put in a spacer here for label height stability self._spacer = tkinter.Label(self._nowFrame, text='', takefocus=False) self._spacer.pack(side=tkinter.LEFT, expand=False, padx=5) self._nowFrame.pack(side=tkinter.TOP, fill=tkinter.X, expand=True) self._hasHistory = False self._histScrl = tkinter.Scrollbar(self._histFrame) self._histScrl.pack(side=tkinter.RIGHT, fill=tkinter.Y) self._histText = tkrotext.ROText(self._histFrame, wrap=tkinter.WORD, takefocus=False, height=10, yscrollcommand=self._histScrl.set) # (use if just tkinter.Text) state=tkinter.DISABLED, takefocus=False, # exportselection=True is the default self._histText.pack(side=tkinter.TOP, fill=tkinter.X, expand=True) self._histScrl.config(command=self._histText.yview) # don't pack this one now - start out with it hidden # self._histFrame.pack(side=tkinter.TOP, fill=tkinter.X) ### Do not pack the main frame here. Let the application do it. ### # At very end of ctor, add the init text to our history self._appendToHistory(text) def _lblDblClk(self, event=None): if self._hasHistory: # change the button appearance self._expBttn.toggle() # or .select() / .deselect() # and then act as if it was clicked self._expand() def _expand(self): ism = self._histFrame.winfo_ismapped() if ism: # need to collapse self._histFrame.pack_forget() if self._expBttnHasTxt: self._expBttn.configure(text='+') else: # need to expand self._histFrame.pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True) # .X) if self._expBttnHasTxt: self._expBttn.configure(text='-') if self._hasHistory: self._histText.see(tkinter.END) def updateIO(self, text=""): """ Update the text portion of the scrolling canvas """ # Update the class variable with the latest text, and append the # new text to the end of the history. self._appendToHistory(text) self._msgLabelVar.set(text) # this is a little debugging "easter egg" if text.find('long debug line') >= 0: self.updateIO( 'and now we are going to talk and talk for a while' + ' about nothing at all because we want a lot of text') self._nowFrame.update_idletasks() def readline(self): """ Set focus to the entry widget and return it's contents """ # Find what had focus up to this point lastFoc = self.focus_get() # Collapse the label as much as possible, it is too big on Linux & OSX lblTxt = self._msgLabelVar.get() lblTxtLen = len(lblTxt.strip()) lblTxtLen -= 3 self._msgLabel.configure(width=min(self._msgLabelMaxWidth, lblTxtLen)) # Enable the entry widget self._entry.configure(state=tkinter.NORMAL, relief=tkinter.SUNKEN, width=15, takefocus=True, highlightthickness=2) self._entry.focus_set() self._entryTyping.set(True) # Wait until they are done entering their answer self._entry.wait_variable(self._entryTyping) # By now they have hit enter ans = self._entry.get().strip() # Clear and disable the entry widget self._entry.delete(0, tkinter.END) self._entry.configure(state=tkinter.DISABLED, takefocus=False, width=1, relief=tkinter.FLAT, highlightthickness=0) self._entryTyping.set(False) # Expand the label back to normal width self._msgLabel.configure(width=self._msgLabelMaxWidth) # list the answer self.updateIO(ans) # return focus if lastFoc: lastFoc.focus_set() # return the answer - important to have the "\n" on it return ans + "\n" def _enteredText(self, event=None): self._entryTyping.set(False) # end the waiting self._expBttn.focus_set() def _appendToHistory(self, txt): # sanity check - need no blank lines in the history if len(txt.strip()) < 1: return # enable widget temporarily so we can add text # self._histText.config(state=tkinter.NORMAL) # self._histText.delete(1.0, END) # add the new text if self._hasHistory: self._histText.insert(tkinter.END, '\n' + txt.strip(), force=True) else: self._histText.insert(tkinter.END, txt.strip(), force=True) self._hasHistory = True # disable it again # self._histText.config(state=tkinter.DISABLED) # show it if self._histFrame.winfo_ismapped(): self._histText.see(tkinter.END) # self._histFrame.update_idletasks() # finally, make sure expand/collapse button is enabled now self._expBttn.configure(state=tkinter.NORMAL) �������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/newWindowHack.py������������������������������������������������������������������0000644�0001750�0001750�00000007223�14212324745�015636� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" This module hacks tkSimpleDialog to make askstring() work even when the root window has been withdrawn. w/o this hack, Python-2.4.3/Tk8.4 locks up for the following code: the dialog is created, but it is withdrawn just like the root window (!) so there is nothing to interact with and the system hangs. import tkinter tk = tkinter.Tk() tk.withdraw() import tkSimpleDialog tkSimpleDialog.askstring("window title", "question?") """ import tkinter.simpledialog from tkinter import Toplevel, Frame def __init__(self, parent, title=None): '''Initialize a dialog. Arguments: parent -- a parent window (the application window) title -- the dialog title ''' Toplevel.__init__(self, parent) if parent.winfo_viewable(): # XXX this condition is the only "fix". self.transient(parent) if title: self.title(title) self.parent = parent self.result = None body = Frame(self) self.initial_focus = self.body(body) body.pack(padx=5, pady=5) self.buttonbox() self.wait_visibility() # window needs to be visible for the grab self.grab_set() if not self.initial_focus: self.initial_focus = self self.protocol("WM_DELETE_WINDOW", self.cancel) if self.parent is not None: self.geometry("+{:d}+{:d}".format(parent.winfo_rootx() + 50, parent.winfo_rooty() + 50)) self.initial_focus.focus_set() self.wait_window(self) tkinter.simpledialog.Dialog.__init__ = __init__ """ Here are some more notes from my "investigation": ==================================================================================== http://mail.python.org/pipermail/python-list/2005-April/275761.html tkinter "withdraw" and "askstring" problem Jeff Epler jepler at unpythonic.net Tue Apr 12 15:58:22 CEST 2005 * Previous message: tkinter "withdraw" and "askstring" problem * Next message: os.open() i flaga lock * Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] The answer has to do with a concept Tk calls "transient". wm transient window ?master? If master is specified, then the window manager is informed that window is a transient window (e.g. pull-down menu) working on behalf of master (where master is the path name for a top-level window). If master is specified as an empty string then window is marked as not being a transient window any more. Otherwise the command returns the path name of s current master, or an empty string if window t currently a transient window. A transient window will mirror state changes in the master and inherit the state of the master when initially mapped. It is an error to attempt to make a window a transient of itself. In tkSimpleDialog, the dialog window is unconditionally made transient for the master. Windows is simply following the documentation: The askstring window "inherit[s] the state of the master [i.e., withdrawn] when initially mapped". The fix is to modify tkSimpleDialog.Dialog.__init__ to only make the dialog transient for its master when the master is viewable. This mirrors what is done in dialog.tcl in Tk itself. You can either change tkSimpleDialog.py, or you can include a new definition of __init__ with these lines at the top, and the rest of the function the same: def __init__(self, parent, title = None): ''' the docstring ... ''' Toplevel.__init__(self, parent) if parent.winfo_viewable(): self.transient(parent) ... # Thanks for being so dynamic, Python! tkSimpleDialog.Dialog.__init__ = __init__; del __init__ Jeff """ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000033�00000000000�010211� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������27 mtime=1649147960.664998 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/noiraf/���������������������������������������������������������������������������0000755�0001750�0001750�00000000000�14223000071�013747� 5����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/noiraf/cl.par���������������������������������������������������������������������0000644�0001750�0001750�00000003264�14214070451�015067� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Dummy CL parameter file # # This file is used when no IRAF is available. # # Variables effecting cl operation. args,s,h,,,,CL command line arguments gcur,*gcur,a,,,,Graphics cursor imcur,*imcur,a,,,,Image cursor ukey,*ukey,a,,,,Global user terminal keyboard keylist abbreviate,b,h,yes,,,Allow abbreviations in operand names? echo,b,h,no,,,Echo CL command input on stderr? ehinit,s,h,"nostandout eol noverify",,,Ehistory options string epinit,s,h,"standout showall",,,Eparam options string keeplog,b,h,no,,,Record all interactive commands in logfile? logfile,f,h,"home$logfile.cl",,,Name of the logfile logmode,s,h,"commands nobackground noerrors notrace",,,Logging control lexmodes,b,h,yes,,,Enable conversational mode menus,b,h,yes,,,Display menu when changing packages? showtype,b,h,no,,,Add task-type suffix in menus? notify,b,h,yes,,,Send done message when bkgrnd task finishes? szprcache,i,h,4,1,10,Size of the process cache version,s,h,"IRAF V2.17",,,IRAF version logver,s,h,"",,,login.cl version logregen,b,h,no,,,Updating of login.cl to current version is advised release,s,h,"2.17",,,IRAF release mode,s,h,ql,,,CL mode of execution (query or query+learn) # auto,s,h,a,,,The next 4 params are read-only. query,s,h,q hidden,s,h,h learn,s,h,l menu,s,h,m # # Handy boolean variables for interactive use. b1,b,h,,,,b1 b2,b,h,,,,b2 b3,b,h,,,,b3 # Handy integer variables for interactive use. i,i,h,,,,i j,i,h,,,,j k,i,h,,,,k # Handy real variables for interactive use. x,r,h,,,,x y,r,h,,,,y z,r,h,,,,z # Handy string variables for interactive use. s1,s,h,,,,s1 s2,s,h,,,,s2 s3,s,h,,,,s3 # Handy parameter for reading lists (text files). list,*s,h,,,,list # Line buffer for list files. line,struct,h,,,,line ... ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/noiraf/clpackage.cl���������������������������������������������������������������0000644�0001750�0001750�00000000322�14214070451�016207� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# IRAF dummy standard system package script task declarations. # # This file is used when no IRAF is available. # task language.pkg = "language$language.cl" task system.pkg = "system$system.cl" system keep ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/noiraf/login.cl�������������������������������������������������������������������0000644�0001750�0001750�00000001520�14214070451�015406� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# LOGIN.CL -- Dummy user login file # # This file is used when no IRAF is available. # set home = envget("PWD") set userid = envget("USER") set uparm = "home$uparm/" stty xterm showtype = yes printf ("\nThis is a minimal PyRAF environment without access to IRAF.\n") printf ("It just defined the bare-bones functionality needed for PyRAF itself.\n\n") clpackage # Default USER package - to be modified by the user package user # Basic foreign tasks from UNIX task $bc $cal $cat $cp $csh $date $df $diff $du $find = "$foreign" task $grep $ls $make $man $mv $nm $od $ps $scp $ssh $sh = "$foreign" task $strings $su $top $vi $emacs $w $wc $less $more = "$foreign" task $sync $pwd $cc $gdb $xc $mkpkg $generic $rtar $wtar = "$foreign" task $tar $bash $tcsh $buglog $who $ssh $scp $mkdir $rm = "$foreign" task $chmod $sort = "$foreign" keep ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/noiraf/system.cl������������������������������������������������������������������0000644�0001750�0001750�00000002016�14214070451�015623� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SYSTEM.CL -- Dummy package script task for the SYSTEM package # # This file is used when no IRAF is available. # package system # These tasks might be useful to convert to Python where no IRAF exists #task cmdstr, # concatenate, # copy, # count, # delete, # directory, # files, # head, # lprint, # match, # mkdir, # movefiles, # mtclean, # $netstatus, # page, # pathnames, # protect, # rename, # sort, # tail, # tee, # touch, # type, # rewind, # unprotect, # help = "system$x_system.e" #hidetask cmdstr #hidetask mtclean task mkscript = "system$mkscript.cl" task $news = "system$news.cl" task allocate = "hlib$allocate.cl" task gripes = "hlib$gripes.cl" task deallocate = "hlib$deallocate.cl" task devstatus = "hlib$devstatus.cl" task $diskspace = "hlib$diskspace.cl" task $spy = "hlib$spy.cl" task $devices = "system$devices.cl" task references = "system$references.cl" task phelp = "system$phelp.cl" keep ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/noiraf/system.par�����������������������������������������������������������������0000644�0001750�0001750�00000000165�14214070451�016012� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Dummy system parameter file # # This file is used when no IRAF is available. # version,s,h,"12-Nov-83" mode,s,h,ql �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/pseteparoption.py�����������������������������������������������������������������0000644�0001750�0001750�00000002475�14214070451�016140� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""pseteparoption.py: module for defining the specific parameter display options to be used for PSETs in the parameter editor task. Code was broken out from eparoption.py. """ # local modules from .tools import eparoption from . import epar class PsetEparOption(eparoption.ActionEparButton): def getButtonLabel(self): # Return the string to show on the button. This happens to be # a great time to set self.psetName # For a PSET self.value is actually an IrafTask object # Use task name to label button self.psetName = self.value.getName() return "PSET " + self.psetName def clicked(self): # use to be called childEparDialog() # Get a reference to the parent TopLevel parentToplevel = self.master.winfo_toplevel() # Don't create multiple windows for the same task for child in parentToplevel.childList: if child.taskName == self.psetName: child.top.deiconify() child.top.tkraise() return childPsetHandle = epar.PyrafEparDialog( self.psetName, parent=self.master_frame, isChild=1, childList=parentToplevel.childList, title="PSET Parameter Editor") parentToplevel.childList.append(childPsetHandle) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/pycmdline.py����������������������������������������������������������������������0000644�0001750�0001750�00000045142�14214070451�015046� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""pycmdline.py -- Python/CL command line interface for Pyraf Provides this functionality: - Command directives: .logfile [filename [append]] .exit .help .complete [ 1 | 0 ] .fulltraceback .clemulate .debug - Shell escapes (!cmd, !!cmd to run in /bin/sh) - CL command-mode execution, triggered by a line that starts with a CL task token and is not followed by other characters indicating it is some other kind of Python statement - Normal Python mode execution (when CL emulation and directives are not triggered) Uses standard code module plus some ideas from cmd.py module (and of course Perry's Monty design.) R. White, 2000 February 20 """ import string import re import os import sys import code import keyword import traceback import linecache from . import iraf from . import irafinst from . import wutil from .tools import minmatch, capable from .pyrafglobals import pyrafDir class CmdConsole(code.InteractiveConsole): """Base class for command console. Similar to InteractiveConsole, but provides local prompt control and hook for simple non-Python command processing. """ def __init__(self, locals=None, filename="<console>", cmddict=None, prompt1=">>> ", prompt2="... ", cmdchars=("a-zA-Z_.", "0-9")): code.InteractiveConsole.__init__(self, locals=locals, filename=filename) self.ps1 = prompt1 self.ps2 = prompt2 if cmddict is None: cmddict = {} self.cmddict = cmddict # cmdchars gives character set for first character, following # characters in the command name # create pattern that puts command in group 'cmd' and matches # optional leading and trailing whitespace self.recmd = re.compile("[ \t]*(?P<cmd>" + "[" + cmdchars[0] + "][" + cmdchars[0] + cmdchars[1] + "]*" + ")[ \t]*") # history is a list of lines entered by user (allocated in blocks) self.history = 100 * [None] self.nhistory = 0 from . import irafcompleter self.completer = irafcompleter.IrafCompleter() def addHistory(self, line): """Append a line to history""" if self.nhistory >= len(self.history): self.history.extend(100 * [None]) self.history[self.nhistory] = line self.nhistory = self.nhistory + 1 def printHistory(self, n=20): """Print last n lines of history""" for i in range(-min(n, self.nhistory), 0): print(self.history[self.nhistory + i]) def interact(self, banner=None): """Emulate the interactive Python console, with extra commands. Also is modified so it does not catch EOFErrors.""" if banner is None: self.write(f"Python {sys.version} on {sys.platform}\n" f"{sys.copyright}\n({self.__class__.__name__})\n") else: self.write(f"{str(banner)}\n") more = 0 # number of consecutive EOFs neofs = 0 # flag indicating whether terminal ID needs to be set needtermid = 1 while True: try: if not sys.stdin.isatty(): prompt = "" elif more: prompt = self.ps2 else: prompt = self.ps1 # !!! prompt = 'curpkg > ' # reset the focus to terminal if necessary wutil.focusController.resetFocusHistory() line = self.raw_input(prompt) if needtermid and prompt: # reset terminal window ID immediately # after first input from terminal wutil.terminal.updateWindowID() needtermid = 0 neofs = 0 # add non-null lines to history if line.strip(): self.addHistory(line) # note that this forbids combination of python & CL # code -- e.g. a for loop that runs CL tasks. if not more: line = self.cmd(line) if line or more: more = self.push(line) except EOFError: # XXX ugly code here -- refers to methods # XXX defined in extensions of this class neofs = neofs + 1 if neofs >= 5: self.write("\nToo many EOFs, exiting now\n") self.do_exit() self.write("\nUse .exit to exit\n" ".help describes executive commands\n") self.resetbuffer() more = 0 except KeyboardInterrupt: self.write("^C\n") self.resetbuffer() more = 0 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): """Hook to handle other commands (this version does nothing)""" return line # put the executive commands in a minimum match dictionary _cmdDict = minmatch.QuietMinMatchDict({ '.help': 'do_help', '.clemulate': 'do_clemulate', '.logfile': 'do_logfile', '.exit': 'do_exit', # 'lo': 'do_exit', '.fulltraceback': 'do_fulltraceback', '.complete': 'do_complete', '.debug': 'do_debug', }) class PyCmdLine(CmdConsole): """Simple Python interpreter with executive commands""" def __init__(self, locals=None, logfile=None, complete=1, debug=0, clemulate=1): CmdConsole.__init__(self, locals=locals, cmddict=_cmdDict, prompt1="--> ", prompt2="... ") self.reword = re.compile('[a-z]*') self.complete = complete self.debug = debug self.clemulate = clemulate self.logfile = None self.lasttrace = None if logfile is not None: if hasattr(logfile, 'write'): self.logfile = logfile elif isinstance(logfile, str): self.do_logfile(logfile) else: self.write('logfile ignored -- not string or filehandle\n') # turn command completion on or off as requested self.do_complete(default=self.complete) # install special error handler for Tk tracebacks if capable.OF_GRAPHICS: from pyraf import pyrafTk pyrafTk.setTkErrorHandler(self.showtraceback) def runsource(self, source, filename="<input>", symbol="single"): """Compile and run some source in the interpreter. Modified from code.py to include logging.""" try: pcode = code.compile_command(source, filename, symbol) except (OverflowError, SyntaxError): # Case 1 self.showsyntaxerror(filename) return 0 if pcode is None: # Case 2 return 1 # Case 3 self.runcode(pcode) if self.logfile: self.logfile.write(source) if source[-1:] != '\n': self.logfile.write('\n') self.logfile.flush() return 0 def do_help(self, line='', i=0): """Print help on executive commands""" if self.debug > 1: self.write(f'do_help: {line[i:]}\n') self.write("""Executive commands (commands can be abbreviated): .exit Exit from Pyraf. .help Print this help message. .logfile [filename [append|overwrite]] If filename is specified, start logging commands to the file. If filename is omitted, turns off logging. The optional append/overwrite argument determines action for existing file (default is to append.) .fulltraceback Print full version of last error traceback. .complete [0|1] Turn command-completion on (default) or off. When on, the tab character acts as the completion character, attempting to complete a partially specified task name, variable name, filename, etc. If the result is ambiguous, a list of the possibilities is printed. Use ^V+tab to insert a tab other than at the line beginning. .clemulate [0|1] Turn CL emulation on (default) or off, which determines whether lines starting with a CL task name are interpreted in CL mode rather than Python mode. .debug [1|0] Set debugging flag. If argument is omitted, default is 1 (debugging on.) """) def do_exit(self, line='', i=0): """Exit from PyRAF and then Python""" if self.debug > 1: self.write(f'do_exit: {line[i:]}\n') # write out history - ignore write errors hfile = os.getenv('HOME', '.') + os.sep + '.pyraf_history' hlen = 1000 # High default. Note this setting itself may cause # confusion between this history and the IRAF history cmd. try: hlen = int(iraf.envget('histfilesize')) except (KeyError, ValueError): pass try: import readline readline.set_history_length(hlen) # hlen<0 means unlimited readline.write_history_file(hfile) # clobber any old version except (ImportError, OSError): pass # graphics wutil.closeGraphics() # leave raise SystemExit() def do_logfile(self, line='', i=0): """Start or stop logging commands""" if self.debug > 1: self.write(f'do_logfile: {line[i:]}\n') args = line[i:].split() if len(args) == 0: # turn off logging (if on) if self.logfile: self.logfile.close() self.logfile = None else: self.write("No log file currently open\n") else: filename = args[0] del args[0] oflag = 'a' if len(args) > 0: if args[0] == 'overwrite': oflag = 'w' del args[0] elif args[0] == 'append': del args[0] if args: self.write(f'Ignoring unknown options: {" ".join(args)}\n') try: oldlogfile = self.logfile self.logfile = open(filename, oflag) if oldlogfile: oldlogfile.close() except OSError as e: self.write(f"error opening logfile {filename}\n{str(e)}\n") return "" def do_clemulate(self, line='', i=0): """Turn CL emulation on or off""" if self.debug > 1: self.write(f'do_clemulate: {line[i:]}\n') self.clemulate = 1 if line[i:] != "": try: self.clemulate = int(line[i:]) except ValueError as e: if self.debug: self.write(str(e) + '\n') pass return "" def do_complete(self, line='', i=0, default=1): """Turn command completion on or off""" if self.debug > 1: self.write(f'do_complete: {line[i:]}\n') self.complete = default if line[i:] != "": try: self.complete = int(line[i:]) except ValueError as e: if self.debug: self.write(str(e) + '\n') pass if self.complete: # set list of executive commands self.completer.executive(_cmdDict.keys()) self.completer.activate() else: self.completer.deactivate() return "" def do_debug(self, line='', i=0): """Turn debug output on or off""" if self.debug > 1: self.write(f'do_debug: {line[i:]}\n') self.debug = 1 if line[i:] != "": try: self.debug = int(line[i:]) except ValueError as e: if self.debug: self.write(str(e) + '\n') pass return "" def do_fulltraceback(self, line='', i=0): """Print full version of last traceback""" if self.debug > 1: self.write(f'do_fulltraceback: {line[i:]}\n') self.showtraceback(reprint=1) return "" 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 """ if self.debug > 1: self.write(f'default: {cmd} - {line[i:]}\n') 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.clemulate: 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.clemulate == 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 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 string.digits and \ line[i] not in 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:] # if we get to here then it looks like CL code if self.debug > 1: self.write(f'CL: {line}\n') try: code = iraf.clExecute(line, locals=self.locals, mode='single') if self.logfile is not None: # log CL code as comment cllines = line.split('\n') for oneline in cllines: self.logfile.write(f'# {oneline}\n') self.logfile.write(code) self.logfile.flush() except: self.showtraceback() return '' def isLocal(self, value): """Returns true if value is local variable""" ff = value.split('.') return ff[0] in self.locals def start(self, banner="Python/CL command line wrapper\n" " .help describes executive commands"): """Start interpreter""" self.interact(banner=banner) def showtraceback(self, reprint=0): """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. """ try: if reprint: if self.lasttrace is None: return type, value, tbmod = self.lasttrace else: type, value, tb = sys.exc_info() linecache.checkcache() sys.last_type = type sys.last_value = value sys.last_traceback = tb tblist = traceback.extract_tb(tb) del tblist[:1] self.lasttrace = type, value, tblist if self.debug: tbmod = tblist else: tbmod = [] for tb1 in tblist: path, filename = os.path.split(tb1[0]) path = os.path.normpath(os.path.join( os.getcwd(), path)) if path[:len(pyrafDir)] != pyrafDir: tbmod.append(tb1) list = traceback.format_list(tbmod) if list: list.insert(0, "Traceback (innermost last):\n") list[len(list):] = traceback.format_exception_only(type, value) finally: tbmod = tblist = tb = None for item in list: self.write(item) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/pyrafTk.py������������������������������������������������������������������������0000644�0001750�0001750�00000001676�14212324745�014514� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""pyrafTk.py: modify tkinter root to print short PyRAF tracebacks R. L. White, 2000 November 17 """ import sys import tkinter from . import wutil class _PyrafTk(tkinter.Tk): """Modified Tk class that prints short pyraf tracebacks""" def __init__(self, function): self._pyraf_showtraceback = function tkinter.Tk.__init__(self) def report_callback_exception(self, exc, val, tb): sys.stderr.write("Exception in tkinter callback\n") sys.last_type = exc sys.last_value = val sys.last_traceback = tb self._pyraf_showtraceback() def setTkErrorHandler(function): """Create Tk root with error handler modified to call function If Tk root already exists, this function has no effect. """ if tkinter._default_root is None and wutil.hasGraphics: try: root = _PyrafTk(function) root.withdraw() except tkinter.TclError: pass ������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/pyrafglobals.py�������������������������������������������������������������������0000644�0001750�0001750�00000002057�14214070451�015545� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""module pyrafglobals.py -- widely used PyRAF constants and objects pyrafDir Directory with these Pyraf programs _use_ecl Flag to turn on ECL mode in PyRAF This is defined so it is safe to say 'from pyrafglobals import *' Broken out from irafglobals.py which was signed "R. White, 2000 January 5" """ import os as _os import sys as _sys _use_ecl = _os.environ.get("PYRAF_USE_ECL", False) # ----------------------------------------------------- # pyrafDir is directory containing this script # ----------------------------------------------------- if __name__ == "__main__": thisfile = _sys.argv[0] else: thisfile = __file__ # follow links to get to the real filename while _os.path.islink(thisfile): thisfile = _os.readlink(thisfile) pyrafDir = _os.path.dirname(thisfile) del thisfile from .tools.irafglobals import userWorkingHome if not pyrafDir: pyrafDir = userWorkingHome # change relative directory paths to absolute and normalize path pyrafDir = _os.path.normpath(_os.path.join(userWorkingHome, pyrafDir)) del userWorkingHome ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/pyraflogo_rgb_web.gif�������������������������������������������������������������0000644�0001750�0001750�00000015207�14203121554�016666� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a����Sj��[obxk{ObrKZ㙦֪ݏ]='ю8w09Hz6AiKXo(B&0XxNT6hD"*EŊ>0hF!T5 tG<<O՚FwO$NRu+ ao ݧ^+*4a1f3nb`rx~̆IDT֩VGB潕_cTUۼtg̙f~z!��,�������pH,Ȥrl:ШtJZجvjA^4ǁn,޸  P{{yn sadg|�koYafx Q G ɓķɘ Пc *^**[f  2`@8'PJ2JD ^RFQ0:itZǓ>*@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&<I &h ~ 6Cpذ �%J0 ,E' PB &p! D`AP0BT̖]2 )@0)W p%L�.0e$D2p%L@"| 0+A 4%PPO: A >L�|�%%H/&B(PR pk%c ATg/@VlO�Z )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:> O<Z)�U-}&<aV\5@&0HB&yݭ7Z0wu?A$ 8 +@1 \ ے"r�|Ct MJHĨ0@k YS f :Q l�^E9yN+z'!ʁXKEYGe8@.6 Hїz1�f~ܦ FMRRlˮ !EBmD$ɽ2o$A "8O hc'XPm`y`,ì A A%NG :h1hF]ȭ˖VDTg"+qg(�;a(K�"&]̂A.9q y<�p`w&gà4,]c4 IN Ւ�ZzHV3[ӂ"3&澞g*8 l%=Q:<�tk%ZP'ZAi/_5v�(:Ajŷ b*d}ܾ4�O�P![<>O@`Jn5<ړj `n](`"0n{ß%�t p}5{ѠoH i \HѾP@p< Lhʴp.C-*p^> �z%_Fp�9�$"�#,4pf&0G:S!ÇVc)bl +qv7/ 5RU |["ԧSP VS (6Cf$Æ#*Bk8o@x/{Kw36LL0!cs>@h8�`z(IFP�va<[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~@?9�P2F`\ANj1')hSW _w0f ra\rq"хZp7 JǗyP$�]C >x eYd�&LV QV \Y ؊ۗt{}y7�e\D\ي Di@dAʉ HW) pt)C@Pqy`/6.V�X5n{�ep�~q�F0`Q˵ }]{pzg[iGDˉ(\Kbck Y 5�@v0zhy<V57%aYԘ)P 0瘚Y X3?6ϴ8p@,� S  ~p | Њi WJ3PoX#�]ajXk� J(EjwY g NHQk5P,zy kʨ6Z�g`n:nFzp^݄֞^Jix8wMH PJQ%@Di�Zɪgwz^jdN0_C�֊0l{ʘ Ő}xh y�P }WeHQrf\){ZMhꜯ ЊxaPd� @hWUjVc;Zkx2:J�fUZ X*=%r&\vxE�l�dp0G[0̰x ~ HT�\;7P">\)dho�gpl/zڢy)VHPɚ"=oYx^`l�!p! x7ʢ4[ g� �%P\U 5�ubG0K*ۢnzK2 z¤`�2l{z~+tf�kLlP ЀkJ㫰{ ICi�:�*)PY2*'禆 X@GK)UID? .s9)%\chgefdF A%�ٷ s nMj+Ñ}/�+g6hGl�4d/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+y�D?\5eP.�V#\crRxVŰ%Ҵ[:U=zHjKJ[kl:n,l_XN\` 9lu*{I:˰m<1@!D:]L)锧͖, L6`0 ! 0j绻ꯅ`�z�t~tOé�'&�s))m`; fJ]znٱܳ۶1 k l]NeYvP�#\"o] `AߘMy K Hm:= iwM0y,g)xO UԚd�:1-]Ȼ nS]muG�0R!��ǘ 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 ~I�h{ 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��;�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/splash.py�������������������������������������������������������������������������0000644�0001750�0001750�00000017320�14214070451�014351� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""splash.py: Display PyRAF splash screen R. White, 2001 Dec 15 """ import os import sys import tkinter from .tools.irafglobals import IrafPkg from . import wutil logo = "pyraflogo_rgb_web.gif" class SplashScreen(tkinter.Toplevel): """Base class for splash screen Subclass and override createWidgets(). In constructor of main window/application call - S = SplashScreen(main=self) (if caller is Toplevel) - S = SplashScreen(main=self.master) (if caller is Frame) - S.Destroy() after you are done creating your widgets etc. Based closely on news posting by Alexander Schliep, 07 Apr 1999 """ def __init__(self, master=None, borderwidth=4, relief=tkinter.RAISED, **kw): tkinter.Toplevel.__init__(self, master, relief=relief, borderwidth=borderwidth, **kw) if self.master.master is not None: # Why? self.master.master.withdraw() self.master.withdraw() self.overrideredirect(1) self.createWidgets() self.after_idle(self.centerOnScreen) self.update() def centerOnScreen(self): self.update_idletasks() xmax = self.winfo_screenwidth() ymax = self.winfo_screenheight() x0 = (xmax - self.winfo_reqwidth()) // 2 y0 = (ymax - self.winfo_reqheight()) // 2 self.geometry(f"+{x0:d}+{y0:d}") def createWidgets(self): # Implement in derived class pass def Destroy(self): self.master.update() self.master.deiconify() self.withdraw() class PyrafSplash(SplashScreen): """PyRAF splash screen Contains an image and one or more text lines underneath. The number of lines is determined by the value of the text argument, which may be a string (for a single line or, with embedded newlines, multiple lines) or a list of strings. The text line(s) can be changed using the write() method. """ def __init__(self, filename=logo, text=None, textcolor="blue", **kw): # look for file in both local directory and this script's directory if not os.path.exists(filename): tfilename = os.path.join(os.path.dirname(__file__), filename) if not os.path.exists(tfilename): raise ValueError(f"Splash image `{filename}' not found") filename = tfilename self.filename = filename self.nlines = 1 self.textcolor = textcolor if text: if isinstance(text, str): text = text.split("\n") self.nlines = len(text) self.initialText = text else: self.initialText = [None] # put focus on this app (Mac only) self.__termWin = None if wutil.hasGraphics and wutil.WUTIL_ON_MAC: self.__termWin = wutil.getFocalWindowID() # the terminal window wutil.forceFocusToNewWindow() # create it SplashScreen.__init__(self, **kw) self.defaultCursor = self['cursor'] self.bind("<Button>", self.killCursor) self.bind("<ButtonRelease>", self.Destroy) def createWidgets(self): """Create pyraf splash image""" self.img = tkinter.PhotoImage(file=self.filename) width = self.img.width() + 20 iheight = self.img.height() height = iheight + 10 + 15 * self.nlines self.canvas = tkinter.Canvas(self, width=width, height=height, background=self["background"]) self.image = self.canvas.create_image(width // 2, 5 + iheight // 2, image=self.img) self.text = self.nlines * [None] minx = 0 font = ("helvetica", 12) for i in range(self.nlines): y = height - (self.nlines - i) * 15 + 8 tval = self.initialText[i] or "" self.text[i] = self.canvas.create_text(width // 2, y, text=tval, fill=self.textcolor, font=font) minx = min(minx, self.canvas.bbox(self.text[i])[0]) if minx < 3: # expand window and recenter all items width = width + (3 - minx) * 2 self.canvas.configure(width=width) self.canvas.coords(self.image, width // 2, 5 + iheight // 2) for i in range(self.nlines): y = height - (self.nlines - i) * 15 + 8 self.canvas.coords(self.text[i], width // 2, y) self.canvas.pack() def write(self, s): """Set text string""" if self.text is None: return if isinstance(s, str): s = s.split("\n") s = s[:len(self.text)] for i in range(len(s)): if s[i] is not None: self.canvas.itemconfigure(self.text[i], text=s[i]) self.update_idletasks() def killCursor(self, event=None): """Set 'pirate' kill cursor on button down""" self['cursor'] = 'pirate' def Destroy(self, event=None): if event: # make sure button release occurred in window # tkinter should take care of this but doesn't if event.x<0 or event.x>=self.winfo_width() or \ event.y<0 or event.y>=self.winfo_height(): self['cursor'] = self.defaultCursor return self.destroy() # disable future writes self.text = None self.update_idletasks() # put focus back on terminal (if set) if self.__termWin: wutil.setFocusTo(self.__termWin) class IrafMonitorSplash(PyrafSplash): """PyRAF splash screen that also acts as IRAF task execution monitor Usually start this by calling the splash() function in this module. """ def __init__(self, label="PyRAF Execution Monitor", **kw): PyrafSplash.__init__(self, text=[None, label], **kw) # self.stack tracks messages displayed in monitor self.stack = [] from . import iraftask iraftask.executionMonitor = self.monitor def monitor(self, task=None): if task is None: # if arg is omitted, restore monitor message to previous value try: self.stack.pop() msg = self.stack[-1] except IndexError: msg = "" else: name = task.getName() if name != 'cl': if isinstance(task, IrafPkg): msg = f"Loading {name}" else: msg = f"Running {name}" else: # cl task message includes input file name try: msg = f"cl {os.path.basename(sys.stdin.name)}" except AttributeError: msg = "cl <pipe>" self.stack.append(msg) self.write(msg) def Destroy(self, event=None): """Shut down window and disable monitor""" from . import iraftask if iraftask.executionMonitor == self.monitor: iraftask.executionMonitor = None PyrafSplash.Destroy(self, event) def splash(label="PyRAF Execution Monitor", background="LightYellow", **kw): """Display the PyRAF splash screen Silently does nothing if tkinter is not usable. """ if wutil.hasGraphics: try: return IrafMonitorSplash(label, background=background, **kw) except tkinter.TclError: pass return None ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/sqliteshelve.py�������������������������������������������������������������������0000644�0001750�0001750�00000013763�14212324745�015604� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This module was taken from https://github.com/devnull255/sqlite-shelve/ # # Copyright (c) 2020 Michael D. Mabin and other contributors # # 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. import pickle import sqlite3 import os class Shelf: """An SQLite implementation of the Python Shelf interface """ def __init__(self, fname, mode): """Open or create an existing sqlite3_shelf """ if mode == 'r': if not os.access(fname, os.R_OK): raise OSError(f'Cannot read {fname}') fname += '?mode=ro' self.readonly = True elif mode in 'cw': if not os.access(fname, os.F_OK): if not os.access(os.path.dirname(fname), os.W_OK): raise OSError(f'Cannot create {fname}') elif not os.access(fname, os.W_OK): raise OSError(f'Cannot write {fname}') self.readonly = False else: raise ValueError(f'Illegal mode {mode}') self.db = sqlite3.connect('file:' + fname, uri=True, isolation_level=None) # create shelf table if it doesn't already exist cursor = self.db.cursor() try: cursor.execute("select * from sqlite_master" " where type = 'table' and tbl_name = 'shelf'") rows = cursor.fetchall() if len(rows) == 0: if self.readonly: raise OSError(f'No table "shelf" in {fname}') cursor.execute("create table shelf" " (id integer primary key autoincrement," " key_str text," " value_str text," " unique(key_str))") finally: cursor.close() def __setitem__(self, key, value): """Set an entry for key to value using pickling """ if self.readonly: raise OSError("Readonly database") pdata = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) cursor = self.db.cursor() try: cursor.execute("insert or replace into shelf (key_str, value_str)" " values (:key,:value)", {'key': key, 'value': sqlite3.Binary(pdata)}) self.db.commit() finally: cursor.close() def get(self, key, default_value=None): """Return an entry for key """ try: return self[key] except KeyError: return default_value def __getitem__(self, key): """Returns an entry for key """ cursor = self.db.cursor() try: cursor.execute("select value_str from shelf" " where key_str = :key", {'key': key}) result = cursor.fetchone() if result: return pickle.loads(result[0]) else: raise KeyError(key) finally: cursor.close() def keys(self): """Return list of keys """ cursor = self.db.cursor() try: cursor.execute('select key_str from shelf') for row in cursor: yield row[0] finally: cursor.close() def values(self): """Return list of keys """ cursor = self.db.cursor() try: cursor.execute('select value_str from shelf') for row in cursor: yield pickle.loads(row[0]) finally: cursor.close() def items(self): """Return list of keys """ cursor = self.db.cursor() try: cursor.execute('select key_str, value_str from shelf') for row in cursor: yield row[0], pickle.loads(row[1]) finally: cursor.close() def __contains__(self, key): """implements in operator if <key> in db """ return key in self.keys() def __iter__(self): return iter(self.keys()) def __len__(self): """ Returns number of entries in shelf """ cursor = self.db.cursor() try: cursor.execute('select count(*) from shelf') row = cursor.fetchone() return row[0] finally: cursor.close() def __delitem__(self, key): """Delete an existing item. """ if self.readonly: raise OSError("Readonly database") cursor = self.db.cursor() try: cursor.execute("delete from shelf where key_str = :key", {'key': key}) finally: cursor.close() def close(self): """Close database and commits changes """ self.db.commit() self.db.close() def open(dbpath, mode): """Create and return a Shelf object """ return Shelf(dbpath + '.sqlite3', mode) def close(db): """Commit changes to the database """ db.close() �������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/sscanfmodule.c��������������������������������������������������������������������0000644�0001750�0001750�00000031165�14203121554�015336� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** Copyright 1991-1995 by Stichting Mathematisch Centrum, Amsterdam, The Netherlands. All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ******************************************************************/ /* * This is a simple C sscanf() analog. sscanf(input, format) parses * the input string according to the format specification and returns * a list of the converted values. The C sscanf() assignment * suppression and maximum field width syntax is supported. An 'l' * size modifier causes integer conversions to return a Python * long rather than a Python integer; it has no effect for float * conversions. * * Conversion specification are of the form: * %[*][<max_width>]['l']<type_character>. * * The following format conversions are supported: * * %c Fixed width character string. * %d Signed integer (leading 0 => octal, 0x => hex). * %f, %g, %e Python float (C double). * %i Same as '%d'. * %l Python long. * %n Number of characters parsed so far. * %o Octal integer. * %s String of non-whitespace characters with leading * whitespace skipped. * %u Unsigned integer. * %x Hexadecimal integer. * %[] Character scan set. * * Parsing of the format string stops when the input string is exhausted, * so format conversion syntax errors will go undetected until there is * enough input to reveal them. * * There are some differences from C sscanf(): * * 1) The %c conversion scans a fixed-width (default 1) character string * rather than a single character, with no special interpretation of * whitespace. For example, '%5c' converts the next 5 characters or * the remainder of the input string, whichever is the shorter, into * a Python string. Some C sscanf() implementations also work this * way. * * 2) If a field width is specified, it sets the maximum number of * characters, starting from the current position, that will be * considered in the conversion. C sscanf() conversions tend to * skip white space before imposing the field width. You can * get the C sscanf() behaviour by inserting a space before the * format specification. * * To install the module in Python, copy the source file into the * Modules directory, add the following line to the Modules/Setup * file: * sscanf sscanfmodule.c * and do a make. * */ #include "Python.h" #include <ctype.h> /* * Size of copy buffer for conversions with a specified field width. */ #define FWBUFSIZE 1023 /* * Flag array for scansets. Array is indexed by character, * with scanset[char] nonzero if char is in the scanset. */ #define BITSPERCHAR (8 * sizeof(char)) static char scanset[1 << BITSPERCHAR]; /* ----------------------------------------------------- */ static char sscanf_sscanf__doc__[] = "" ; static PyObject * sscanf_sscanf( PyObject *self, PyObject *args ) { char c, *fmt, *input, *inptr; char *s, *start, *end, mfwbuf[FWBUFSIZE + 1]; int i, base, err, doassign, inplen, ellmod, sense, inprem, width; long lval; double dval; PyObject *item, *list; if (!PyArg_ParseTuple(args, "ss;input_string, format_string", &input, &fmt)) { return NULL; } /* Create an empty list object to hold scanned values */ if ((list = PyList_New(0)) == NULL) return NULL; inptr = input; inplen = strlen(input); /* * Parse the format string and apply the matches/conversions * found to the input string as we go. Inside the loop, a * break generally means that a mismatch of some kind has * occurred, and the show is over. */ while ((c = *fmt++) != '\0') { /* Skip whitespace? */ if (isspace(c)) { while (isspace(*inptr)) inptr++; continue; } /* Ordinary character or '%%' match? */ if (c != '%' || *fmt == '%') { if (inptr[0] != c || inptr[1] == '\0') break; /* Mismatch: finished */ inptr++; if (c == '%') fmt++; /* Skip the second '%' */ continue; } /* * Start of conversion specification. */ c = *fmt++; /* Assignment suppression? */ doassign = 1; if (c == '*') { c = *fmt++; doassign = 0; } /* Field width? */ for (width = 0; isdigit(c); c = *fmt++) width = width*10 + (c - '0'); /* 'l' modifier? */ ellmod = 0; if (c == 'l' && strchr("defgilnoux", *fmt) != NULL) { ellmod = 1; c = *fmt++; } if (width < 0) { PyErr_SetString(PyExc_ValueError, "insane field width"); goto fail; } else if (width == 0) { start = inptr; } else if (width > FWBUFSIZE) { PyErr_SetString(PyExc_ValueError, "field width exeeds internal limits"); goto fail; } else { /* Copy to max-field-width-buffer */ inprem = inplen - (inptr - input); if (width > inprem) width = inprem; start = mfwbuf; if (width > 0) memcpy(start, inptr, width); start[width] = '\0'; } /* * We have a format conversion - apply it * to the input string. */ errno = 0; item = NULL; if (c == 'c') { /* Character field */ if (*start == '\0') break; if (width < 1) width = 1; if (doassign) item = PyUnicode_FromStringAndSize(start, width); inptr += width; } else if (c == 'd' || c == 'i' || c == 'l' || c == 'o' || c == 'u' || c == 'x') { /* Python integer or long */ for (s = start; isspace(*s); s++) ; if (*s == '\0') break; /* Fake out '%l' */ if (c == 'l') ellmod = 1; if (c == 'o') { base = 8; } else if (c == 'x') { base = 16; } else { base = 0; } if (ellmod) { /* Result is a Python long */ if (c == 'u' && *s == '-') /* Oops */ break; item = PyLong_FromString(s, &end, base); if (item == NULL) goto fail; if (end == s) { Py_DECREF(item); break; } if (doassign == 0) { Py_DECREF(item); item = NULL; } } else { /* Result is a Python integer */ if (c == 'u') lval = PyOS_strtoul(s, &end, base); else lval = PyOS_strtol(s, &end, base); if (errno != 0) { PyErr_SetString(PyExc_OverflowError, (c == 'u') ? "unsigned overflow" : "integer overflow"); goto fail; } if (end == s) break; if (doassign) item = PyLong_FromLong(lval); } inptr += end - start; } else if (c == 'f' || c == 'g' || c == 'e') { /* Python float */ for (s = start; isspace(*s); s++) ; if (*s == '\0') break; dval = strtod(s, &end); if (errno != 0) { PyErr_SetString(PyExc_OverflowError, "float overflow"); goto fail; } if (end == s) break; if (doassign) item = PyFloat_FromDouble(dval); inptr += end - start; } else if (c == 'n') { /* Characters scanned so far */ if (doassign) { lval = inptr - input; if (ellmod) { item = PyLong_FromLong(lval); } else { item = PyLong_FromLong(lval); } } } else if (c == 's') { /* Non-whitespace string */ for (s = start; isspace(*s); s++) ; for (end = s; *end && !isspace(*end); end++) ; if (end == s) break; if (doassign) item = PyUnicode_FromStringAndSize(s, end - s); inptr += end - start; } else if (c == '[') { /* Character scanset */ c = *fmt++; if (c == '^') { c = *fmt++; sense = 0; memset(scanset, 1, sizeof(scanset)); } else { sense = 1; memset(scanset, 0, sizeof(scanset)); } scanset[0] = 0; /* Don't match trailing '\0' */ if (c == ']') { scanset[Py_CHARMASK(c)] = sense; c = *fmt++; } while (1) { if (c == '\0') { PyErr_SetString(PyExc_ValueError, "bad scanset specification"); goto fail; } else if (c == ']') { /* End of scanset */ break; } else if (fmt[0] != '-' || fmt[1] == ']' || fmt[1] < c) { scanset[Py_CHARMASK(c)] = sense; c = *fmt++; } else { /* Character range */ for (i = Py_CHARMASK(c); i <= Py_CHARMASK(fmt[1]); i++) { scanset[i] = sense; } fmt += 2; c = *fmt++; } } /* Match characters in the scanset */ for (end = start; scanset[Py_CHARMASK(*end)]; end++) ; if (end == start) break; if (doassign) item = PyUnicode_FromStringAndSize(start, end - start); inptr += end - start; } else { PyErr_SetString(PyExc_ValueError, "unrecognised format conversion"); goto fail; } /* * Add the converted value to the result list. */ if (doassign) { if (item == NULL) goto fail; err = PyList_Append(list, item); Py_DECREF(item); if (err < 0) goto fail; } } /* Return the list of converted values */ return list; fail: Py_DECREF(list); return NULL; } /* List of methods defined in the module */ static struct PyMethodDef sscanf_methods[] = { {"sscanf", sscanf_sscanf, 1, sscanf_sscanf__doc__}, {NULL, NULL} /* sentinel */ }; /* * Initialization function for the module * - must be called initsscanf on linux/mac * - must be called initsscanfmodule on windows * (idiots.) */ #ifdef _WIN32 #define initsscanf initsscanfmodule #define PyInit_sscanf PyInit_sscanfmodule #endif static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "sscanf", NULL, -1, sscanf_methods, NULL, NULL, NULL, NULL, }; PyObject* PyInit_sscanf(void) { /* Create the module and add the functions */ PyObject *m; m = PyModule_Create(&moduledef); return m; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/subproc.py������������������������������������������������������������������������0000644�0001750�0001750�00000101055�14214070451�014533� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Run a subprocess and communicate with it via stdin, stdout, and stderr. Requires that platform supports, eg, posix-style os.pipe and os.fork. Subprocess class features: - provides non-blocking stdin and stderr reads - provides subprocess stop and continue, kill-on-deletion - provides detection of subprocess startup failure - Subprocess objects have nice, informative string rep (as every good object ought). - RecordFile class provides record-oriented IO for file-like stream objects. """ __version__ = "Revision: 1.7r " # Id: subproc.py,v 1.7r 1998 # Originally by ken manheimer, ken.manheimer@nist.gov, jan 1995. # Major revisions by R. White, rlw@stsci.edu, 1999 Jan 23 # Prior art: Initially based python code examples demonstrating usage of pipes # and subprocesses, primarily one by jose pereira. # Implementation notes: # - I'm not using the fcntl module to implement non-blocking file descriptors, # because i don't know what all in it is portable and what is not. I'm not # about to provide for different platform contingencies - at that extent, the # effort would be better spent hacking 'expect' into python. # - Todo? - Incorporate an error-output handler approach, where error output is # checked on regular IO, when a handler is defined, and passed to the # handler (eg for printing) immediately as it shows... # - Detection of failed subprocess startup is a gross kludge, at present. # - new additions (1.3, 1.4): # - Readbuf, taken from donn cave's iobuf addition, implements non-blocking # reads based solely on os.read with select, while capitalizing big-time on # multi-char read chunking. # - Subproc deletion frees up pipe file descriptors, so they're not exhausted. # # ken.manheimer@nist.gov import errno import os import select import signal import sys import time OS_HAS_FORK = hasattr(os, 'fork') class SubprocessError(Exception): pass class Subprocess: """Run and communicate asynchronously with a subprocess. control_(stderr,stdout,stdin) determines whether the corresponding I/O streams of the subprocess are captured or not. The default is the capture stdout/stdin but not stderr. Provides non-blocking reads in the form of .readPendingChars and .readPendingLine. .readline will block until it gets a complete line. There are corresponding readXXX routines, to read from the subprocess stderr stream.""" def __init__(self, cmd, control_stderr=0, control_stdout=1, control_stdin=1, expire_noisily=0, in_fd=0, out_fd=1, err_fd=2, maxChunkSize=1024): """Launch a subprocess, given command string COMMAND.""" self.cmd = cmd self.pid = None # pid of child, as known by parent only self.expire_noisily = expire_noisily # Announce subproc destruction? self.control_stderr = control_stderr self.control_stdout = control_stdout self.control_stdin = control_stdin self.maxChunkSize = maxChunkSize self.in_fd, self.out_fd, self.err_fd = in_fd, out_fd, err_fd self.fork() def fork(self, cmd=None): """Fork a subprocess with designated COMMAND (default, self.cmd).""" if cmd: self.cmd = cmd if isinstance(self.cmd, str): cmd = self.cmd.split() else: cmd = self.cmd self.cmd = " ".join(cmd) # Create pipes self.parentPipes = [] childPipes = [] if self.control_stdout: pRc, cWp = os.pipe() # parent-read-child, child-write-parent self.parentPipes.append(pRc) childPipes.append(cWp) if self.control_stdin: cRp, pWc = os.pipe() # child-read-parent, parent-write-child self.parentPipes.append(pWc) childPipes.append(cRp) if self.control_stderr: pRe, cWe = os.pipe() # parent-read-error, child-write-error self.parentPipes.append(pRe) childPipes.append(cWe) self.pid = os.fork() if self.pid == 0: # CHILD #### parentErr = os.dup( self.err_fd) # Preserve handle on *parent* stderr # Reopen stdin, out, err, on pipe ends: if self.control_stdin: os.dup2(cRp, self.in_fd) # cRp = sys.stdin if self.control_stdout: os.dup2(cWp, self.out_fd) # cWp = sys.stdout if self.control_stderr: os.dup2(cWe, self.err_fd) # cWe = sys.stderr # close parent ends of pipes for i in self.parentPipes: os.close(i) # Ensure (within reason) stray file descriptors are closed fdmax = max(256, parentErr) if self.parentPipes: fdmax = max(fdmax, max(self.parentPipes)) rclose = list(range(fdmax + 1)) excludes = [self.in_fd, self.out_fd, self.err_fd, parentErr] for i in excludes + self.parentPipes: rclose.remove(i) os_close = os.close os_error = os.error for i in rclose: try: os_close(i) except os_error: pass try: os.execvp(cmd[0], cmd) os._exit(1) # Shouldn't get here except os.error as e: if self.control_stderr: os.dup2(parentErr, 2) # Reconnect to parent's stderr sys.stderr.write(f"**execvp failed, '{str(e)}'**\n") os._exit(1) else: # PARENT ### # Connect to the child's file descriptors and close child ends of pipes self.toChild = self.readbuf = self.errbuf = None self.toChild_fdlist = [] self.fromChild_fdlist = [] if self.control_stdin: self.toChild_fdlist.append(pWc) self.toChild = pWc if self.control_stderr: self.errbuf = ReadBuf(pRe, self.maxChunkSize) self.fromChild_fdlist.append(pRe) if self.control_stdout: self.readbuf = ReadBuf(pRc, self.maxChunkSize) self.fromChild_fdlist.append(pRc) # close child ends of pipes for i in childPipes: os.close(i) try: # this is useless since the child may not have erred yet pid, err = os.waitpid(self.pid, os.WNOHANG) except os.error as xxx_todo_changeme1: (errnum, msg) = xxx_todo_changeme1.args if errnum == 10: raise SubprocessError(f"Subprocess '{self.cmd}' failed.") else: raise SubprocessError(f"Subprocess '{self.cmd}' failed " f"[{errnum:d}]: {msg}") if pid != self.pid: # flag indicating process is still running self.return_code = None elif err == 0: # Child has exited already but not in error, so we won't # shut down the pipes or say anything more at this point. # Set return_code so we don't call waitpid again. self.return_code = 0 else: # Process exited with an error. Clean up immediately and # raise an exception. self._cleanUp(err) sig = err & 0xff rc = (err & 0xff00) >> 8 self.return_code = rc if sig: raise SubprocessError( f"Child process '{self.cmd}' killed by signal {sig:d} " f"with return code {rc:d}") else: raise SubprocessError( f"Child process '{self.cmd}' exited " f"with return code {rc:d}") ### Write input to subprocess ### def write(self, strval, timeout=10, printtime=2): """Write a bytes or string to the subprocess. Times out (and raises an exception) if the process is not ready in timeout seconds. Prints a message indicating that it is waiting every printtime seconds. """ if not self.pid: raise SubprocessError(f"No child process for '{self.cmd}'") if not self.control_stdin: raise SubprocessError( f"Haven't grabbed subprocess input stream for {self}.") # See if subprocess is ready for write. # Add a wait in case subprocess is still starting up or is # otherwise temporarily unable to respond. # Loop with message if wait takes longer than that, until wait # exceeds the total timeout. if timeout < 0: timeout = 0 if printtime > timeout: printtime = timeout totalwait = 0 try: while totalwait <= timeout: ## if totalwait: print "waiting for subprocess..." totalwait = totalwait + printtime if select.select([], self.toChild_fdlist, [], printtime)[1]: if not isinstance(strval, bytes): strval = strval.encode() if os.write(self.toChild, strval) != len(strval): raise SubprocessError(f"Write error to {self}") return # ===> raise SubprocessError(f"Write to {self} blocked") except OSError as e: raise SubprocessError( f"Select error for {self}: file descriptors " f"{self.toChild_fdlist}\n{str(e)}") def writeline(self, line=''): """Write STRING, with added newline termination, to subprocess.""" self.write(line + '\n') def closeOutput(self): """Close write pipe to subprocess (signals EOF to subprocess)""" if not self.control_stdin: raise SubprocessError( f"Haven't grabbed subprocess input stream for {self}.") os.close(self.toChild) self.parentPipes.remove(self.toChild) self.toChild = None self.toChild_fdlist = [] self.control_stdin = 0 ### Get output from subprocess ### def read(self, n=None): # returns bytes """Read N chars (blocking), or all pending if no N specified.""" if not self.control_stdout: raise SubprocessError( f"Haven't grabbed subprocess output stream for {self}.") if n is None: return self.readPendingChars() else: return self.readbuf.read(n) def readErr(self, n=None): # returns bytes """Read N chars from stderr (blocking), or all pending if no N specified.""" if not self.control_stderr: raise SubprocessError( f"Haven't grabbed subprocess error stream for {self}.") if n is None: return self.readPendingErrChars() else: return self.errbuf.read(n) def readPendingChars(self, max=None): # returns bytes """Read all currently pending subprocess output as a single string.""" if not self.control_stdout: raise SubprocessError( f"Haven't grabbed subprocess output stream for {self}.") return self.readbuf.readPendingChars(max) def readPendingErrChars(self, max=None): # returns bytes """Read all currently pending subprocess error output as a single string.""" if not self.control_stderr: raise SubprocessError( f"Haven't grabbed subprocess error stream for {self}.") return self.errbuf.readPendingChars(max) def readPendingLine(self): # returns bytes """Read currently pending subprocess output, up to a complete line (newline inclusive).""" if not self.control_stdout: raise SubprocessError( f"Haven't grabbed subprocess output stream for {self}.") return self.readbuf.readPendingLine() def readPendingErrLine(self): # returns bytes """Read currently pending subprocess error output, up to a complete line (newline inclusive).""" if not self.control_stderr: raise SubprocessError( f"Haven't grabbed subprocess error stream for {self}.") return self.errbuf.readPendingLine() def readline(self): """Return next complete line of subprocess output, blocking until then.""" if not self.control_stdout: raise SubprocessError( f"Haven't grabbed subprocess output stream for {self}.") return self.readbuf.readline() def readlineErr(self): """Return next complete line of subprocess error output, blocking until then.""" if not self.control_stderr: raise SubprocessError( f"Haven't grabbed subprocess error stream for {self}.") return self.errbuf.readline() ### Subprocess Control ### def active(self, checkpipes=1): """True if subprocess is alive and kicking. If checkpipes is true, also checks the pipes to the process to make sure they are still OK. """ status = self.status(boolean=1) if status and checkpipes: try: readable, writable, errors = select.select( self.fromChild_fdlist, self.toChild_fdlist, [], 0) except OSError: status = 0 return status def status(self, boolean=0): """Return string indicating whether process is alive or dead.""" active = 0 if not self.cmd: status = 'sans command' elif not self.pid: status = 'sans process' elif not self.cont(): status = f"(unresponding) '{self.cmd}'" else: status = f"'{self.cmd}'" active = 1 if boolean: return active else: return status def wait(self, timeout=0): """Wait timeout seconds for process to die. Returns true if process is dead (and was reaped), false if alive.""" if self.return_code is not None: # process completed during startup, so just clean up now self._cleanUp(self.return_code << 8) return 1 # Try a few times to reap the process with waitpid: totalwait = timeout deltawait = timeout / 1000.0 if deltawait < 0.01: deltawait = 0.01 while totalwait >= 0: pid, err = os.waitpid(self.pid, os.WNOHANG) if pid: self._cleanUp(err) return 1 time.sleep(deltawait) totalwait = totalwait - deltawait return 0 def _cleanUp(self, err): """Cleanup after process is done""" if not self.pid: return if self.expire_noisily: self._noisy_print(err) self._closePipes() self.pid = None self.return_code = (err & 0xff00) >> 8 def _closePipes(self): """Close all pipes from parent to child""" for p in self.parentPipes: try: os.close(p) except os.error: pass self.parentPipes = [] self.toChild = None self.toChild_fdlist = [] self.fromChild_fdlist = [] self.control_stdin = 0 self.control_stdout = 0 self.control_stderr = 0 def _noisy_print(self, err): sig = err & 0xff rc = (err & 0xff00) >> 8 if sig == 0: sigval = '' elif sig == signal.SIGTERM: sigval = 'TERMinated ' elif sig == signal.SIGKILL: sigval = 'KILLed ' else: sigval = f'Signal {sig:d} ' if rc: retval = f'Status {rc:d} ' else: retval = '' sys.stderr.write( f"\n({sigval}subproc {self.pid:d} '{self.cmd}' " f"{retval}/ {hex(id(self))[2:]})\n") sys.stderr.flush() def stop(self, verbose=False): """Signal subprocess with STOP (17), returning 'stopped' if ok, or 0 otherwise.""" try: os.kill(self.pid, signal.SIGSTOP) except os.error: if verbose: print(f"Stop failed for '{self.cmd}' - '{sys.exc_info()[1]}'") return 0 if verbose: print(f"Stopped '{self.cmd}'") return 'stopped' def cont(self, verbose=False): """Signal subprocess with CONT (19), returning 'continued' if ok, or 0 otherwise.""" try: os.kill(self.pid, signal.SIGCONT) except os.error: if verbose: print(f"Continue failed for '{self.cmd}' - '{sys.exc_info()[1]}'") return 0 if verbose: print(f"Continued '{self.cmd}'") return 'continued' def die(self): """Send process PID signal SIG (default 9, 'kill'), returning once it is successfully reaped. SubprocessError is raised if process is not successfully killed.""" # close pipes to child self._closePipes() if not self.pid: raise SubprocessError("No process") elif not self.cont(): raise SubprocessError(f"Can't signal subproc {self}") # Try sending first a TERM and then a KILL signal. sigs = [('TERM', signal.SIGTERM), ('KILL', signal.SIGKILL)] for sig in sigs: try: os.kill(self.pid, sig[1]) except os.error: # keep trying pass # done if we can reap the process; else try next signal if self.wait(0.5): return # ===> # Only got here if subprocess is not gone: raise SubprocessError( f"Failed kill of subproc {self.pid:d}, '{self.cmd}', " f"with signals {[x[0] for x in sigs]}") def __del__(self): """Terminate the subprocess""" if self.pid and not self.wait(0): self.die() def __repr__(self): status = self.status() return '<Subprocess ' + status + ', at ' + hex(id(self))[2:] + '>' ############################################################################# ##### Non-blocking read operations ##### ############################################################################# class ReadBuf: """Output buffer for non-blocking reads on selectable files like pipes and sockets. Init with a file descriptor for the file.""" def __init__(self, fd, maxChunkSize=1024): """Encapsulate file descriptor FD, with optional MAX_READ_CHUNK_SIZE (default 1024).""" if fd < 0: raise ValueError("File descriptor fd is negative") self.fd = fd self.eof = 0 # May be set with stuff still in .buf self.buf = '' self.chunkSize = maxChunkSize # Biggest read chunk, default 1024. def fileno(self): return self.fd def readPendingChars(self, max=None): """Consume uncomsumed output from FILE, or empty string if nothing pending. Returns bytes.""" if (max is not None) and (max <= 0): return b'' # ===> if self.buf: if max and (len(self.buf) > max): got, self.buf = self.buf[0:max], self.buf[max:] else: got, self.buf = self.buf, b'' return got # ===> if self.eof: return b'' # ===> try: sel = select.select([self.fd], [], [self.fd], 0) except OSError: # select error occurs if self.fd been closed # treat like EOF self.eof = 1 return b'' # ===> if sel[0]: got = os.read(self.fd, self.chunkSize) if got: if max and (len(got) > max): self.buf = got[max:] return got[:max] # ===> else: return got # ===> else: self.eof = 1 return b'' # ===> else: return b'' # ===> def readPendingLine(self, block=0): """Return pending output from FILE, up to first newline (inclusive). Returns bytes. Does not block unless optional arg BLOCK is true. This may return a partial line if the input line is longer than chunkSize (default 1024) characters.""" if self.buf: to = self.buf.find(b'\n') if to != -1: got, self.buf = self.buf[:to + 1], self.buf[to + 1:] return got # ===> got, self.buf = self.buf, b'' elif self.eof: return b'' # ===> else: got = b'' # 'got' contains the (former) contents of the buffer, but it # doesn't include a newline. fdlist = [self.fd] if block: # wait indefinitely for input waittime = None else: # don't wait at all waittime = 0 while True: # (we'll only loop if block set) try: sel = select.select(fdlist, [], fdlist, waittime) except OSError: # select error occurs if self.fd has been closed # treat like EOF self.eof = 1 return got if sel[0]: newgot = os.read(self.fd, self.chunkSize) if newgot: got = got + newgot to = got.find(b'\n') if to != -1: got, self.buf = got[:to + 1], got[to + 1:] return got # ===> else: # return partial line on EOF self.eof = 1 return got # ===> if not block: return got # ===> # otherwise - no newline, blocking requested, no eof - loop. # ==^ def readline(self): """Return next output line from file, blocking until it is received.""" return self.readPendingLine(1) # ===> def read(self, nchars): """Read nchars from input, blocking until they are available. Returns a shorter string on EOF. Returns bytes.""" if nchars <= 0: return b'' if self.buf: if len(self.buf) >= nchars: got, self.buf = self.buf[:nchars], self.buf[nchars:] return got # ===> got, self.buf = self.buf, b'' elif self.eof: return b'' # ===> else: got = b'' fdlist = [self.fd] while True: try: sel = select.select(fdlist, [], fdlist) except OSError: # select error occurs if self.fd has been closed # treat like EOF self.eof = 1 return got if sel[0]: newgot = os.read(self.fd, self.chunkSize) if newgot: got = got + newgot if len(got) >= nchars: got, self.buf = got[:nchars], got[nchars:] return got # ===> else: self.eof = 1 return got else: print('Select returned without input?') ############################################################################# ##### Encapsulated reading and writing ##### ############################################################################# # Encapsulate messages so the end can be unambiguously identified, even # when they contain multiple, possibly empty lines. class RecordFile: """Encapsulate stream object for record-oriented IO. Particularly useful when dealing with non-line oriented communications over pipes, eg with subprocesses.""" # Message is written preceded by a line containing the message length. def __init__(self, f): self.file = f def write_record(self, s): "Write so self.read knows exactly how much to read." f = self.__dict__['file'] f.write(f"{len(s)}\n{s}") if hasattr(f, 'flush'): f.flush() def read_record(self): "Read and reconstruct message as prepared by self.write." f = self.__dict__['file'] line = f.readline()[:-1] if line: try: l = int(line) except ValueError: raise OSError(f"corrupt {self.__class__.__name__} " "file structure") return f.read(l) else: # EOF. return '' def __getattr__(self, attr): """Implement characteristic file object attributes.""" f = self.__dict__['file'] if hasattr(f, attr): return getattr(f, attr) else: raise AttributeError(attr) def __repr__(self): return (f"<{self.__class__.__name__} " f"of {self.__dict__['file']} " f"at {hex(id(self))[2:]}>") def record_trial(s): """Exercise encapsulated write/read with an arbitrary string. Raise IOError if the string gets distorted through transmission!""" from io import StringIO sf = StringIO() c = RecordFile(sf) c.write(s) c.seek(0) r = c.read() show = f" start:\t {repr(s)}\n end:\t {repr(r)}\n" if r != s: raise OSError(f"String distorted:\n{show}") ############################################################################# ##### Run a subprocess with Python I/O redirection ##### ############################################################################# # This runs a command rather like os.system does, but it redirects # the I/O using the current Python sys.stdin, sys.stdout, sys.stderr # filehandles. def systemRedir(cmd): """Run the command as a subprocess with Python I/O redirection in effect cmd can be a string or a list of strings. """ # XXX should trap errors and return status? process = RedirProcess(cmd) try: process.run() except KeyboardInterrupt: process.die() sys.stderr.write(f"\nKilled process `{process.cmd}'\n") sys.stderr.flush() raise return process.return_code # run subprocess with Python I/O redirection in a subshell def subshellRedir(cmd, shell=None): """Run the command in a subshell with Python I/O redirection in effect cmd should be a simple string with the command and its arguments. shell is the shell to use -- default is value of SHELL environment variable or /bin/sh if SHELL is not defined. """ if OS_HAS_FORK: shell = shell or os.environ.get('SHELL') or '/bin/sh' return systemRedir((shell, "-c", cmd)) else: return _wrapSubprocess(cmd) def _wrapSubprocess(cmdline): """ This function is set up mostly for use on Windows (w/out Cygwin) since that is the only mode it is currently expected to be used in. """ # subprocess.call should work for most commands import subprocess return subprocess.call(cmdline, shell=True) # this waits class RedirProcess(Subprocess): """Run a system command with I/O redirected using sys.stdin/out/err""" def __init__(self, cmd, expire_noisily=0): # grab only streams for currently redirected IO doIn = doOut = doErr = 1 if sys.stdin == sys.__stdin__: doIn = 0 if sys.stdout == sys.__stdout__: doOut = 0 if sys.stderr == sys.__stderr__: doErr = 0 # even if none are redirected, run it as subprocess # so it can be interrupted with ^C # initialize the process Subprocess.__init__(self, cmd, expire_noisily=expire_noisily, control_stderr=doErr, control_stdout=doOut, control_stdin=doIn) def run(self, timeout=5): """Copy the subprocess I/O to the Python stdin/out/err filehandles""" if not self.pid: return doIn = self.control_stdin doOut = self.control_stdout doErr = self.control_stderr while (doIn or doOut or doErr): try: readable, writable, errors = select.select( self.fromChild_fdlist, self.toChild_fdlist, [], timeout) except OSError as e: # select error occurs if a file descriptor has been closed # this should not happen -- raise an exception raise SubprocessError( f"Select error for {self}: file descriptors " f"{self.toChild_fdlist + self.fromChild_fdlist}\n" f"{str(e)}") if readable: # stderr is first in fromChild_fdlist (if present) if doErr and (self.fromChild_fdlist[0] in readable): # stderr s = self.readPendingErrChars() # returns bytes if s: if hasattr(sys.stderr, "buffer"): sys.stderr.buffer.write(s) else: sys.stderr.write(s.decode()) sys.stderr.flush() else: # EOF os.close(self.fromChild_fdlist[0]) self.parentPipes.remove(self.fromChild_fdlist[0]) del self.fromChild_fdlist[0] doErr = 0 self.control_stderr = 0 else: # stdout s = self.readPendingChars() # returns bytes if s: if hasattr(sys.stdout, "buffer"): sys.stdout.buffer.write(s) else: sys.stdout.write(s.decode()) sys.stdout.flush() else: # EOF if doErr: os.close(self.fromChild_fdlist[1]) self.parentPipes.remove(self.fromChild_fdlist[1]) del self.fromChild_fdlist[1] else: os.close(self.fromChild_fdlist[0]) self.parentPipes.remove(self.fromChild_fdlist[0]) del self.fromChild_fdlist[0] doOut = 0 self.control_stdout = 0 elif writable: # stdin try: if hasattr(sys.stdin, "buffer"): s = sys.stdin.buffer.read(self.maxChunkSize) else: s = sys.stdin.read(self.maxChunkSize) if s: try: self.write(s) # inside, converts PY3K str to bytes except OSError as xxx_todo_changeme: # broken pipe may be OK # just call it an EOF and see what happens (errnum, msg) = xxx_todo_changeme.args # broken pipe may be OK # just call it an EOF and see what happens if errnum == errno.EPIPE: raise EOFError() else: raise else: # EOF if readline returns null os.close(self.toChild) self.parentPipes.remove(self.toChild) del self.toChild_fdlist[0] self.toChild = None doIn = 0 self.control_stdin = 0 except EOFError: os.close(self.toChild) self.parentPipes.remove(self.toChild) del self.toChild_fdlist[0] self.toChild = None doIn = 0 self.control_stdin = 0 else: # timeout, just continue pass # Finished with the IO under our control, but task could # still be running. Wait for it to finish. while not self.wait(5): # see whether something bad happened if not self.active(): sys.stderr.write(self.status() + '\n') sys.stderr.flush() self.die() break �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000033�00000000000�010211� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������27 mtime=1649147960.668998 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/����������������������������������������������������������������������������0000755�0001750�0001750�00000000000�14223000071�013633� 5����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/__init__.py�����������������������������������������������������������������0000644�0001750�0001750�00000000000�14203121554�015742� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000033�00000000000�010211� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������27 mtime=1649147960.668998 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/�����������������������������������������������������������������������0000755�0001750�0001750�00000000000�14223000071�014544� 5����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psdump_prow_250.ps�����������������������������������������������������0000644�0001750�0001750�00000055343�14203121554�020077� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-2.0 /devppi 300 def /userppi 72 def /pagewidth 8.5 def /devpixtouser { userppi mul devppi div } def /pagetolandscape 90 def /setcoords { pagewidth userppi mul 0 translate pagetolandscape rotate 1 devpixtouser 1 devpixtouser scale } def /setjoins { 1 setlinejoin 1 setlinecap } def erasepage initgraphics setcoords setjoins /getpoint { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } def /m { getpoint moveto } def /d { getpoint lineto } def 02 setlinewidth 05 setlinewidth m F\Fw d GxFw d GxGS d GxFw d IXFw d IXGS d IXFw d JyFw d JyGS d JyFw d LYFw d LYGS d LYFw d MzFw d MzGn d MzFw stroke 02 setlinewidth m MNFA d MNE\ m MOFA d MOE^ m MQFE d MQE\ m MQFE d MLE? d MIE} m MGE\ d MWE\ m MNE^ d MJE\ m MNE` d MLE\ m MQE` d MRE\ m MQE^ d MTE\ m MCEN m MrFE d MnFC d MjE} d MiEs d MiEm d MjEd d MnE^ d MrE\ d MvE\ d MzE^ d M}Ed d M?Em d M?Es d M}E} d MzFC d MvFE d MrFE m MnFA d MlE} d MjEu d MjEk d MlEd d MnE` m MzE` d M|Ed d M}Ek d M}Eu d M|E} d MzFA m MrFE d MoFC d MnE? d MlEu d MlEk d MnEb d MoE^ d MrE\ m MvE\ d MyE^ d MzEb d M|Ek d M|Eu d MzE? d MyFC d MvFE m MgEN m NWFE d NRFC d NOE} d NNEs d NNEm d NOEd d NRE^ d NWE\ d NZE\ d N_E^ d NbEd d NdEm d NdEs d NbE} d N_FC d NZFE d NWFE m NRFA d NQE} d NOEu d NOEk d NQEd d NRE` m N_E` d NaEd d NbEk d NbEu d NaE} d N_FA m NWFE d NTFC d NRE? d NQEu d NQEk d NREb d NTE^ d NWE\ m NZE\ d N]E^ d N_Eb d NaEk d NaEu d N_E? d N]FC d NZFE m NLEN stroke 05 setlinewidth m MzFw d OZFw d OZGS d OZFw d P{Fw d P{GS d P{Fw d R[Fw d R[GS d R[Fw d S{Fw d S{GS d S{Fw d U\Fw d U\Gn d U\Fw stroke 02 setlinewidth m ThE} d ThE{ d TjE{ d TjE} d ThE} m ThE? d TjE? d TkE} d TkE{ d TjEy d ThEy d TfE{ d TfE} d ThFA d TjFC d TnFE d TuFE d TyFC d T{FA d T}E} d T}Ey d T{Eu d TvEq d TnEm d TkEk d ThEg d TfEb d TfE\ m TyFA d T{E} d T{Ey d TyEu m TuFE d TxFC d TyE} d TyEy d TxEu d TuEq d TnEm m TfE` d ThEb d TkEb d TsE` d TyE` d T}Eb m TkEb d TsE^ d TyE^ d T{E` m TkEb d TsE\ d TyE\ d T{E^ d T}Eb d T}Ef m TeEN m UUFE d UPFC d UME} d UKEs d UKEm d UMEd d UPE^ d UUE\ d UXE\ d U]E^ d U`Ed d UaEm d UaEs d U`E} d U]FC d UXFE d UUFE m UPFA d UNE} d UMEu d UMEk d UNEd d UPE` m U]E` d U^Ed d U`Ek d U`Eu d U^E} d U]FA m UUFE d UQFC d UPE? d UNEu d UNEk d UPEb d UQE^ d UUE\ m UXE\ d U[E^ d U]Eb d U^Ek d U^Eu d U]E? d U[FC d UXFE m UIEN m UyFE d UtFC d UqE} d UpEs d UpEm d UqEd d UtE^ d UyE\ d U|E\ d VAE^ d VDEd d VFEm d VFEs d VDE} d VAFC d U|FE d UyFE m UtFA d UsE} d UqEu d UqEk d UsEd d UtE` m VAE` d VCEd d VDEk d VDEu d VCE} d VAFA m UyFE d UvFC d UtE? d UsEu d UsEk d UtEb d UvE^ d UyE\ m U|E\ d V@E^ d VAEb d VCEk d VCEu d VAE? d V@FC d U|FE m UnEN stroke 05 setlinewidth m U\Fw d V|Fw d V|GS d V|Fw d X]Fw d X]GS d X]Fw d Y}Fw d Y}GS d Y}Fw d [^Fw d [^GS d [^Fw d \~Fw d \~Gn d \~Fw stroke 02 setlinewidth m \JE} d \JE{ d \LE{ d \LE} d \JE} m \JE? d \LE? d \ME} d \ME{ d \LEy d \JEy d \IE{ d \IE} d \JFA d \LFC d \QFE d \WFE d \\FC d \]E? d \]Ey d \\Eu d \WEs m \ZFC d \\E? d \\Ey d \ZEu m \UFE d \YFC d \ZE? d \ZEy d \YEu d \UEs m \REs d \WEs d \ZEq d \]Em d \_Ei d \_Ed d \]E` d \\E^ d \WE\ d \QE\ d \LE^ d \JE` d \IEd d \IEf d \JEg d \LEg d \MEf d \MEd d \LEb d \JEb m \\Em d \]Ei d \]Ed d \\E` m \UEs d \YEq d \ZEo d \\Ei d \\Ed d \ZE^ d \WE\ m \JEf d \JEd d \LEd d \LEf d \JEf m \GEN m \wFE d \rFC d \oE} d \mEs d \mEm d \oEd d \rE^ d \wE\ d \zE\ d \?E^ d ]BEd d ]DEm d ]DEs d ]BE} d \?FC d \zFE d \wFE m \rFA d \qE} d \oEu d \oEk d \qEd d \rE` m \?E` d ]@Ed d ]BEk d ]BEu d ]@E} d \?FA m \wFE d \tFC d \rE? d \qEu d \qEk d \rEb d \tE^ d \wE\ m \zE\ d \}E^ d \?Eb d ]@Ek d ]@Eu d \?E? d \}FC d \zFE m \lEN m ]\FE d ]WFC d ]TE} d ]REs d ]REm d ]TEd d ]WE^ d ]\E\ d ]_E\ d ]dE^ d ]gEd d ]hEm d ]hEs d ]gE} d ]dFC d ]_FE d ]\FE m ]WFA d ]UE} d ]TEu d ]TEk d ]UEd d ]WE` m ]dE` d ]eEd d ]gEk d ]gEu d ]eE} d ]dFA m ]\FE d ]XFC d ]WE? d ]UEu d ]UEk d ]WEb d ]XE^ d ]\E\ m ]_E\ d ]bE^ d ]dEb d ]eEk d ]eEu d ]dE? d ]bFC d ]_FE m ]QEN stroke 05 setlinewidth m \~Fw d ^_Fw d ^_GS d ^_Fw d _?Fw d _?GS d _?Fw d a`Fw d a`GS d a`Fw d c@Fw d c@GS d c@Fw d daFw d daGn d daFw stroke 02 setlinewidth m cxE? d cxE\ m cyFA d cyE^ m c{FE d c{E\ m c{FE d ciEg d dCEg m csE\ d d@E\ m cxE^ d ctE\ m cxE` d cvE\ m c{E` d c|E\ m c{E^ d c~E\ m ciEN m dYFE d dTFC d dQE} d dPEs d dPEm d dQEd d dTE^ d dYE\ d d\E\ d daE^ d ddEd d dfEm d dfEs d ddE} d daFC d d\FE d dYFE m dTFA d dSE} d dQEu d dQEk d dSEd d dTE` m daE` d dcEd d ddEk d ddEu d dcE} d daFA m dYFE d dVFC d dTE? d dSEu d dSEk d dTEb d dVE^ d dYE\ m d\E\ d d_E^ d daEb d dcEk d dcEu d daE? d d_FC d d\FE m dNEN m d~FE d dyFC d dvE} d dtEs d dtEm d dvEd d dyE^ d d~E\ d eAE\ d eFE^ d eIEd d eKEm d eKEs d eIE} d eFFC d eAFE d d~FE m dyFA d dxE} d dvEu d dvEk d dxEd d dyE` m eFE` d eGEd d eIEk d eIEu d eGE} d eFFA m d~FE d d{FC d dyE? d dxEu d dxEk d dyEb d d{E^ d d~E\ m eAE\ d eDE^ d eFEb d eGEk d eGEu d eFE? d eDFC d eAFE m dsEN stroke 05 setlinewidth m daFw d fAFw d fAGS d fAFw d gaFw d gaGS d gaFw d iBFw d iBGS d iBFw d jbFw d jbGS d jbFw d lCFw d lCGn d lCFw stroke 02 setlinewidth m kPFE d kMEq d kPEu d kUEw d kZEw d k_Eu d kbEq d kcEk d kcEg d kbEb d k_E^ d kZE\ d kUE\ d kPE^ d kOE` d kMEd d kMEf d kOEg d kPEg d kREf d kREd d kPEb d kOEb m k`Eq d kbEm d kbEf d k`Eb m kZEw d k]Eu d k_Es d k`Em d k`Ef d k_E` d k]E^ d kZE\ m kOEf d kOEd d kPEd d kPEf d kOEf m kPFE d k`FE m kPFC d k]FC m kPFA d kWFA d k]FC d k`FE m kLEN m k{FE d kwFC d ktE} d krEs d krEm d ktEd d kwE^ d k{E\ d k?E\ d lCE^ d lGEd d lHEm d lHEs d lGE} d lCFC d k?FE d k{FE m kwFA d kuE} d ktEu d ktEk d kuEd d kwE` m lCE` d lEEd d lGEk d lGEu d lEE} d lCFA m k{FE d kxFC d kwE? d kuEu d kuEk d kwEb d kxE^ d k{E\ m k?E\ d lBE^ d lCEb d lEEk d lEEu d lCE? d lBFC d k?FE m kpEN m l`FE d l[FC d lXE} d lWEs d lWEm d lXEd d l[E^ d l`E\ d lcE\ d lhE^ d lkEd d lmEm d lmEs d lkE} d lhFC d lcFE d l`FE m l[FA d lZE} d lXEu d lXEk d lZEd d l[E` m lhE` d ljEd d lkEk d lkEu d ljE} d lhFA m l`FE d l]FC d l[E? d lZEu d lZEk d l[Eb d l]E^ d l`E\ m lcE\ d lfE^ d lhEb d ljEk d ljEu d lhE? d lfFC d lcFE m lUEN stroke 05 setlinewidth m lCFw d l}Fw m l}Fw d l}GY d laGY d l}GY d l}Hv d laHv d l}Hv d l}JR d laJR d l}JR d l}Ko d laKo d l}Ko d l}ML d lEML d l}ML d l}Ni d laNi d l}Ni d l}PF d laPF d l}PF d l}Qc d laQc d l}Qc d l}R? d laR? d l}R? d l}T\ d lET\ d l}T\ d l}Uy d laUy d l}Uy d l}WV d laWV d l}WV d l}Xs d laXs d l}Xs d l}ZP d laZP d l}ZP d l}[l d lE[l d l}[l d l}]I d la]I d l}]I d l}^f d la^f d l}^f d l}`C d la`C d l}`C d l}a` d laa` d l}a` d l}b} d lEb} d l}b} d l}dZ d ladZ d l}dZ d l}dZ m F\Fw d F\GY d FxGY d F\GY d F\Hv d FxHv d F\Hv d F\JR d FxJR d F\JR d F\Ko d FxKo d F\Ko d F\ML d GTML d F\ML stroke 02 setlinewidth m D]MX d D]MV d D_MV d D_MX d D]MX m D]MZ d D_MZ d D`MX d D`MV d D_MT d D]MT d D\MV d D\MX d D]M\ d D_M^ d DdM_ d DjM_ d DoM^ d DpM\ d DrMX d DrMT d DpMP d DlML d DdMH d D`MF d D]MB d D\L| d D\Lw m DoM\ d DpMX d DpMT d DoMP m DjM_ d DmM^ d DoMX d DoMT d DmMP d DjML d DdMH m D\L{ d D]L| d D`L| d DhL{ d DoL{ d DrL| m D`L| d DhLy d DoLy d DpL{ m D`L| d DhLw d DoLw d DpLy d DrL| d DrM@ m DZLi m EDM_ d E@ML d EDMP d EHMR d EMMR d ERMP d EUML d EWMF d EWMB d EUL| d ERLy d EMLw d EHLw d EDLy d EBL{ d E@L~ d E@M@ d EBMB d EDMB d EEM@ d EEL~ d EDL| d EBL| m ESML d EUMH d EUM@ d ESL| m EMMR d EPMP d ERMN d ESMH d ESM@ d ERL{ d EPLy d EMLw m EBM@ d EBL~ d EDL~ d EDM@ d EBM@ m EDM_ d ESM_ m EDM^ d EPM^ m EDM\ d EJM\ d EPM^ d ESM_ m D?Li m EoM_ d EjM^ d EgMX d EeMN d EeMH d EgL~ d EjLy d EoLw d ErLw d EwLy d EzL~ d E{MH d E{MN d EzMX d EwM^ d ErM_ d EoM_ m EjM\ d EhMX d EgMP d EgMF d EhL~ d EjL{ m EwL{ d ExL~ d EzMF d EzMP d ExMX d EwM\ m EoM_ d EkM^ d EjMZ d EhMP d EhMF d EjL| d EkLy d EoLw m ErLw d EuLy d EwL| d ExMF d ExMP d EwMZ d EuM^ d ErM_ m EdLi stroke 05 setlinewidth m F\ML d F\Ni d FxNi d F\Ni d F\PF d FxPF d F\PF d F\Qc d FxQc d F\Qc d F\R? d FxR? d F\R? d F\T\ d GTT\ d F\T\ stroke 02 setlinewidth m D_Tp d D\T\ d D_T` d DdTb d DhTb d DmT` d DpT\ d DrTV d DrTR d DpTM d DmTI d DhTG d DdTG d D_TI d D]TK d D\TO d D\TQ d D]TR d D_TR d D`TQ d D`TO d D_TM d D]TM m DoT\ d DpTX d DpTQ d DoTM m DhTb d DlT` d DmT^ d DoTX d DoTQ d DmTK d DlTI d DhTG m D]TQ d D]TO d D_TO d D_TQ d D]TQ m D_Tp d DoTp m D_Tn d DlTn m D_Tl d DeTl d DlTn d DoTp m DZSy m EJTp d EETn d EBTh d E@T^ d E@TX d EBTO d EETI d EJTG d EMTG d ERTI d EUTO d EWTX d EWT^ d EUTh d ERTn d EMTp d EJTp m EETl d EDTh d EBT` d EBTV d EDTO d EETK m ERTK d ESTO d EUTV d EUT` d ESTh d ERTl m EJTp d EGTn d EETj d EDT` d EDTV d EETM d EGTI d EJTG m EMTG d EPTI d ERTM d ESTV d EST` d ERTj d EPTn d EMTp m D?Sy m EoTp d EjTn d EgTh d EeT^ d EeTX d EgTO d EjTI d EoTG d ErTG d EwTI d EzTO d E{TX d E{T^ d EzTh d EwTn d ErTp d EoTp m EjTl d EhTh d EgT` d EgTV d EhTO d EjTK m EwTK d ExTO d EzTV d EzT` d ExTh d EwTl m EoTp d EkTn d EjTj d EhT` d EhTV d EjTM d EkTI d EoTG m ErTG d EuTI d EwTM d ExTV d ExT` d EwTj d EuTn d ErTp m EdSy stroke 05 setlinewidth m F\T\ d F\Uy d FxUy d F\Uy d F\WV d FxWV d F\WV d F\Xs d FxXs d F\Xs d F\ZP d FxZP d F\ZP d F\[l d GT[l d F\[l stroke 02 setlinewidth m D\\@ d D\[t m Dr\@ d Dr[z d Dp[t d Dj[j d Dh[g d Dg[_ d Dg[W m Dh[i d Dg[e d De[_ d De[W m Dp[t d Dh[j d De[e d Dd[_ d Dd[W d Dg[W m D\[x d D][| d D`\@ d Dd\@ d Dl[z d Do[z d Dp[| d Dr\@ m D_[| d D`[~ d Dd[~ d Dg[| m D\[x d D][z d D`[| d Dd[| d Dl[z m DZ[I m ED\@ d E@[l d ED[p d EH[r d EM[r d ER[p d EU[l d EW[g d EW[c d EU[] d ER[Y d EM[W d EH[W d ED[Y d EB[[ d E@[_ d E@[a d EB[c d ED[c d EE[a d EE[_ d ED[] d EB[] m ES[l d EU[i d EU[a d ES[] m EM[r d EP[p d ER[n d ES[i d ES[a d ER[[ d EP[Y d EM[W m EB[a d EB[_ d ED[_ d ED[a d EB[a m ED\@ d ES\@ m ED[~ d EP[~ m ED[| d EJ[| d EP[~ d ES\@ m D?[I m Eo\@ d Ej[~ d Eg[x d Ee[n d Ee[i d Eg[_ d Ej[Y d Eo[W d Er[W d Ew[Y d Ez[_ d E{[i d E{[n d Ez[x d Ew[~ d Er\@ d Eo\@ m Ej[| d Eh[x d Eg[p d Eg[g d Eh[_ d Ej[[ m Ew[[ d Ex[_ d Ez[g d Ez[p d Ex[x d Ew[| m Eo\@ d Ek[~ d Ej[z d Eh[p d Eh[g d Ej[] d Ek[Y d Eo[W m Er[W d Eu[Y d Ew[] d Ex[g d Ex[p d Ew[z d Eu[~ d Er\@ m Ed[I stroke 05 setlinewidth m F\[l d F\]I d Fx]I d F\]I d F\^f d Fx^f d F\^f d F\`C d Fx`C d F\`C d F\a` d Fxa` d F\a` d F\b} d GTb} d F\b} stroke 02 setlinewidth m DAcL d DAbg m DBcL d DBbi m DDcP d DDbg m DDcP d C?cJ d C|cH m Czbg d DJbg m DAbi d C}bg m DAbk d C?bg m DDbk d DEbg m DDbi d DGbg m CubZ m DecP d D`cN d D]cH d D\b? d D\by d D]bo d D`bi d Debg d Dhbg d Dmbi d Dpbo d Drby d Drb? d DpcH d DmcN d DhcP d DecP m D`cL d D_cH d D]cA d D]bw d D_bo d D`bk m Dmbk d Dobo d Dpbw d DpcA d DocH d DmcL m DecP d DbcN d D`cJ d D_cA d D_bw d D`bm d Dbbi d Debg m Dhbg d Dlbi d Dmbm d Dobw d DocA d DmcJ d DlcN d DhcP m DZbZ m EJcP d EEcN d EBcH d E@b? d E@by d EBbo d EEbi d EJbg d EMbg d ERbi d EUbo d EWby d EWb? d EUcH d ERcN d EMcP d EJcP m EEcL d EDcH d EBcA d EBbw d EDbo d EEbk m ERbk d ESbo d EUbw d EUcA d EScH d ERcL m EJcP d EGcN d EEcJ d EDcA d EDbw d EEbm d EGbi d EJbg m EMbg d EPbi d ERbm d ESbw d EScA d ERcJ d EPcN d EMcP m D?bZ m EocP d EjcN d EgcH d Eeb? d Eeby d Egbo d Ejbi d Eobg d Erbg d Ewbi d Ezbo d E{by d E{b? d EzcH d EwcN d ErcP d EocP m EjcL d EhcH d EgcA d Egbw d Ehbo d Ejbk m Ewbk d Exbo d Ezbw d EzcA d ExcH d EwcL m EocP d EkcN d EjcJ d EhcA d Ehbw d Ejbm d Ekbi d Eobg m Erbg d Eubi d Ewbm d Exbw d ExcA d EwcJ d EucN d ErcP m EdbZ stroke 05 setlinewidth m F\b} d F\dZ d FxdZ d F\dZ d F\dZ m F\dZ d GxdZ d Gxc~ d GxdZ d IXdZ d IXc~ d IXdZ d JydZ d Jyc~ d JydZ d LYdZ d LYc~ d LYdZ d MzdZ d Mzcb d MzdZ d OZdZ d OZc~ d OZdZ d P{dZ d P{c~ d P{dZ d R[dZ d R[c~ d R[dZ d S{dZ d S{c~ d S{dZ d U\dZ d U\cb d U\dZ d V|dZ d V|c~ d V|dZ d X]dZ d X]c~ d X]dZ d Y}dZ d Y}c~ d Y}dZ d [^dZ d [^c~ d [^dZ d \~dZ d \~cb d \~dZ d ^_dZ d ^_c~ d ^_dZ d _?dZ d _?c~ d _?dZ d a`dZ d a`c~ d a`dZ d c@dZ d c@c~ d c@dZ d dadZ d dacb d dadZ d fAdZ d fAc~ d fAdZ d gadZ d gac~ d gadZ d iBdZ d iBc~ d iBdZ d jbdZ d jbc~ d jbdZ d lCdZ d lCcb d lCdZ d l}dZ stroke 02 setlinewidth m VEDV d VGD\ d VGDP d VEDV d VBDZ d U?D\ d U{D\ d UvDZ d UsDV d UrDR d UpDL d UpDC d UrC} d UsCy d UvCu d U{Cs d U?Cs d VBCu d VECy d VGC} m UuDV d UsDR d UrDL d UrDC d UsC} d UuCy m U{D\ d UxDZ d UuDT d UsDL d UsDC d UuC{ d UxCu d U{Cs m UoCe m V`DN d V[DL d VXDH d VVDC d VVC? d VXCy d V[Cu d V`Cs d VcCs d VhCu d VkCy d VlC? d VlDC d VkDH d VhDL d VcDN d V`DN m VYDH d VXDD d VXC} d VYCy m ViCy d VkC} d VkDD d ViDH m V`DN d V]DL d V[DJ d VYDD d VYC} d V[Cw d V]Cu d V`Cs m VcCs d VfCu d VhCw d ViC} d ViDD d VhDJ d VfDL d VcDN m VUCe m W@D\ d W@Cs m WCDZ d WCCu m VyD\ d WED\ d WECs m VyCs d WLCs m V|D\ d W@DZ m V~D\ d W@DX m W@Cu d V|Cs m W@Cw d V~Cs m WECw d WGCs m WECu d WJCs m VyCe m WWDN d WWC} d WXCw d WYCu d W\Cs d WaCs d WcCu d WeCw d WfC{ m WXDL d WXC{ d WYCw m WRDN d WYDN d WYC{ d W[Cu d W\Cs m WfDN d WfCs d WmCs m WhDL d WhCu m WbDN d WiDN d WiCs m WTDN d WWDL m WUDN d WWDJ m WiCw d WjCs m WiCu d WlCs m WRCe m W?DN d W?Cs m X@DL d X@Cu m W{DN d XBDN d XBCs m XBDF d XCDJ d XDDL d XGDN d XJDN d XMDL d XNDJ d XODD d XOCs m XMDJ d XNDD d XNCu m XJDN d XLDL d XMDF d XMCs m XODF d XQDJ d XRDL d XTDN d XXDN d XZDL d X\DJ d X]DD d X]Cs m XZDJ d X\DD d X\Cu m XXDN d XYDL d XZDF d XZCs m W{Cs d XECs m XICs d XSCs m XWCs d XaCs m W}DN d W?DL m W~DN d W?DJ m W?Cu d W}Cs m W?Cw d W~Cs m XBCw d XCCs m XBCu d XDCs m XMCu d XJCs m XMCw d XLCs m XOCw d XQCs m XOCu d XRCs m XZCu d XXCs m XZCw d XYCs m X]Cw d X^Cs m X]Cu d X_Cs m W{Ce m XoDN d XoCs m XpDL d XpCu m XjDN d XrDN d XrCs m XrDF d XsDJ d XtDL d XwDN d X{DN d X~DL d Y@DJ d YADD d YACs m X~DJ d Y@DD d Y@Cu m X{DN d X}DL d X~DF d X~Cs m XjCs d XvCs m XzCs d YECs m XlDN d XoDL m XmDN d XoDJ m XoCu d XlCs m XoCw d XmCs m XrCw d XsCs m XrCu d XtCs m X~Cu d X{Cs m X~Cw d X}Cs m YACw d YCCs m YACu d YDCs m XjCe m YTDi m YTCe m ZCDd d Y?D` d Y{DZ d YwDR d YuDH d YuDA d YwCw d Y{Co d Y?Ci d ZCCe m Y{DX d YyDR d YwDJ d YwC? d YyCw d Y{Cq m Y?D` d Y}D\ d Y{DV d YyDJ d YyC? d Y{Cs d Y}Cm d Y?Ci m YrCe m ZSDN d ZSCe m ZTDL d ZTCg m ZNDN d ZVDN d ZVCe m ZVDH d ZWDL d ZZDN d Z^DN d ZbDL d ZeDH d ZgDC d ZgC? d ZeCy d ZbCu d Z^Cs d ZZCs d ZWCu d ZVCy m ZdDH d ZeDD d ZeC} d ZdCy m Z^DN d ZaDL d ZbDJ d ZdDD d ZdC} d ZbCw d ZaCu d Z^Cs m ZNCe d ZZCe m ZPDN d ZSDL m ZQDN d ZSDJ m ZSCg d ZPCe m ZSCi d ZQCe m ZVCi d ZWCe m ZVCg d ZYCe m ZNCe m Z{D\ d Z{DX d [@DX d [@D\ d Z{D\ m Z~D\ d Z~DX m Z{DZ d [@DZ m Z{DN d Z{Cs m Z~DL d Z~Cu m ZtDN d [@DN d [@Cs m ZtCs d [GCs m ZwDN d Z{DL m ZyDN d Z{DJ m Z{Cu d ZwCs m Z{Cw d ZyCs m [@Cw d [BCs m [@Cu d [ECs m ZtCe m [QDN d [`Cs m [RDN d [bCs m [TDN d [dCs m [bDL d [RCu m [MDN d [XDN m []DN d [gDN m [MCs d [WCs m [\Cs d [gCs m [ODN d [RDL m [WDN d [TDL m [_DN d [bDL m [eDN d [bDL m [RCu d [OCs m [RCu d [UCs m [`Cu d []Cs m [bCu d [eCs m [MCe m [wDC d \IDC d \IDF d \HDJ d \FDL d \ADN d [~DN d [yDL d [uDH d [tDC d [tC? d [uCy d [yCu d [~Cs d \ACs d \FCu d \ICy m \HDD d \HDF d \FDJ m [wDH d [uDD d [uC} d [wCy m \FDC d \FDH d \DDL d \ADN m [~DN d [zDL d [yDJ d [wDD d [wC} d [yCw d [zCu d [~Cs m [rCe m \\D\ d \\Cs m \_DZ d \_Cu m \UD\ d \aD\ d \aCs m \UCs d \hCs m \XD\ d \\DZ m \ZD\ d \\DX m \\Cu d \XCs m \\Cw d \ZCs m \aCw d \cCs m \aCu d \fCs m \UCe m ]BDJ d ]DDN d ]DDF d ]BDJ d ]@DL d \}DN d \uDN d \rDL d \pDJ d \pDF d \rDC d \uDA d \~C? d ]BC} d ]DCw m \rDL d \pDF m \rDD d \uDC d \~DA d ]BC? m ]DC} d ]BCu m \pDJ d \rDF d \uDD d \~DC d ]BDA d ]DC} d ]DCw d ]BCu d \~Cs d \wCs d \tCu d \rCw d \pC{ d \pCs d \rCw m \nCe m ]SDd d ]WD` d ][DZ d ]_DR d ]aDH d ]aDA d ]_Cw d ][Co d ]WCi d ]SCe m ][DX d ]]DR d ]_DJ d ]_C? d ]]Cw d ][Cq m ]WD` d ]YD\ d ][DV d ]]DJ d ]]C? d ][Cs d ]YCm d ]WCi m ]OCe m Ukes d UkeJ d UseJ m Umeq d UmeL m Uges d Uoes d UoeJ m Uke_ d Ujec d Ugee d Udee d U_ec d U\e_ d U[eZ d U[eV d U\eP d U_eL d UdeJ d UgeJ d UjeL d UkeP m U^e_ d U\e\ d U\eT d U^eP m Udee d Uaec d U_ea d U^e\ d U^eT d U_eN d UaeL d UdeJ m Uhes d Ukeq m Ujes d Ukeo m UoeN d UpeJ m UoeL d UreJ m UYd| m VDeZ d VVeZ d VVe] d VUea d VSec d VNee d VKee d VFec d VCe_ d VAeZ d VAeV d VCeP d VFeL d VKeJ d VNeJ d VSeL d VVeP m VUe\ d VUe] d VSea m VDe_ d VCe\ d VCeT d VDeP m VSeZ d VSe_ d VRec d VNee m VKee d VHec d VFea d VDe\ d VDeT d VFeN d VHeL d VKeJ m U?d| m Vdee d VoeJ m Vfee d VoeN m Vhee d VpeN m Vyec d VpeN d VoeJ m Vcee d Vmee m Vree d V|ee m Vcee d Vhea m Vkee d Vhec m Vvee d Vyec m V{ee d Vyec m Vcd| m WNe{ d WNeB m WTe{ d WTeB m W[ei d W[ek d WYek d WYeg d W\eg d W\ek d W[eo d WYeq d WTes d WNes d WIeq d WFem d WFeg d WGec d WLe_ d WVe\ d WYeZ d W[eV d W[eP d WYeL m WGeg d WIec d WLea d WVe] d WYe\ d W[eX m WIeq d WGem d WGei d WIee d WLec d WVe_ d W[e\ d W\eX d W\eR d W[eN d WYeL d WTeJ d WNeJ d WIeL d WGeN d WFeR d WFeV d WIeV d WIeR d WGeR d WGeT m WDd| m Wnee d Wnd| m Woec d Wod~ m Wiee d Wqee d Wqd| m Wqe_ d Wrec d Wuee d Wxee d W}ec d X@e_ d XAeZ d XAeV d X@eP d W}eL d WxeJ d WueJ d WreL d WqeP m W~e_ d X@e\ d X@eT d W~eP m Wxee d W{ec d W}ea d W~e\ d W~eT d W}eN d W{eL d WxeJ m Wid| d Wud| m Wkee d Wnec m Wlee d Wnea m Wnd~ d Wkd| m Wne@ d Wld| stroke m Wld| m Wqe@ d Wrd| m Wqd~ d Wtd| m Wid| m XVes d XVeo d X[eo d X[es d XVes m XYes d XYeo m XVeq d X[eq m XVee d XVeJ m XYec d XYeL m XOee d X[ee d X[eJ m XOeJ d XbeJ m XRee d XVec m XTee d XVea m XVeL d XReJ m XVeN d XTeJ m X[eN d X]eJ m X[eL d X_eJ m XOd| m Xkee d X{eJ m Xmee d X}eJ m Xnee d X~eJ m X}ec d XmeL m Xhee d Xsee m Xxee d YAee m XheJ d XreJ m XveJ d YAeJ m Xjee d Xmec m Xree d Xnec m Xzee d X}ec m Y@ee d X}ec m XmeL d XjeJ m XmeL d XpeJ m X{eL d XxeJ m X}eL d Y@eJ m Xhd| m YTee d YRec d YRea d YTe_ d YWe_ d YYea d YYec d YWee d YTee m YTec d YTea d YWea d YWec d YTec m YTeP d YReN d YReL d YTeJ d YWeJ d YYeL d YYeN d YWeP d YTeP m YTeN d YTeL d YWeL d YWeN d YTeN m YMd| m Ydf@ m Ydd| m ZHee d ZHeJ m ZJec d ZJeL m ZCee d ZLee d ZLeJ m ZYea d ZYec d ZWec d ZWe_ d Z[e_ d Z[ec d ZYee d ZUee d ZRec d ZNe_ d ZLeZ m ZCeJ d ZReJ m ZEee d ZHec m ZGee d ZHea m ZHeL d ZEeJ m ZHeN d ZGeJ m ZLeN d ZNeJ m ZLeL d ZPeJ m ZCd| m Zoee d Zjec d Zge_ d ZeeZ d ZeeV d ZgeP d ZjeL d ZoeJ d ZreJ d ZveL d ZzeP d Z{eV d Z{eZ d Zze_ d Zvec d Zree d Zoee m Zhe_ d Zge\ d ZgeT d ZheP m ZxeP d ZzeT d Zze\ d Zxe_ m Zoee d Zkec d Zjea d Zhe\ d ZheT d ZjeN d ZkeL d ZoeJ m ZreJ d ZueL d ZveN d ZxeT d Zxe\ d Zvea d Zuec d Zree m Zcd| m [Kee d [PeJ m [Lee d [PeP m [Nee d [ReP m [Vee d [ReP d [PeJ m [Vee d [[eJ m [Wee d [[eP m [Vee d [Yee d []eP m [aec d []eP d [[eJ m [Hee d [Ree m []ee d [eee m [Hee d [Lec m [Pee d [Nec m [^ee d [aec m [dee d [aec m [Hd| m [sf@ m [sd| m \Uek d \Uei d \Vei d \Vek d \Uek m \Uem d \Vem d \Xek d \Xei d \Veg d \Ueg d \Sei d \Sek d \Ueo d \Veq d \[es d \aes d \feq d \heo d \iek d \ieg d \hec d \ce_ d \[e\ d \XeZ d \UeV d \SeP d \SeJ m \feo d \hek d \heg d \fec m \aes d \eeq d \fek d \feg d \eec d \ae_ d \[e\ m \SeN d \UeP d \XeP d \`eN d \feN d \ieP m \XeP d \`eL d \feL d \heN m \XeP d \`eJ d \feJ d \heL d \ieP d \ieT m \Rd| m \{es d \xe_ d \{ec d ]@ee d ]Eee d ]Iec d ]Me_ d ]NeZ d ]NeV d ]MeP d ]IeL d ]EeJ d ]@eJ d \{eL d \yeN d \xeR d \xeT d \yeV d \{eV d \}eT d \}eR d \{eP d \yeP m ]Ke_ d ]Me\ d ]MeT d ]KeP m ]Eee d ]Hec d ]Iea d ]Ke\ d ]KeT d ]IeN d ]HeL d ]EeJ m \yeT d \yeR d \{eR d \{eT d \yeT m \{es d ]Kes m \{eq d ]Heq m \{eo d ]Aeo d ]Heq d ]Kes m \vd| m ]fes d ]aeq d ]^ek d ]]ea d ]]e\ d ]^eR d ]aeL d ]feJ d ]ieJ d ]neL d ]qeR d ]se\ d ]sea d ]qek d ]neq d ]ies d ]fes m ]aeo d ]`ek d ]^ec d ]^eZ d ]`eR d ]aeN m ]neN d ]peR d ]qeZ d ]qec d ]pek d ]neo m ]fes d ]ceq d ]aem d ]`ec d ]`eZ d ]aeP d ]ceL d ]feJ m ]ieJ d ]leL d ]neP d ]peZ d ]pec d ]nem d ]leq d ]ies m ][d| m F\GO d FaGN d FfGU d FkGW d FoGO d FtGU d FyGL d F~GQ d GCGW d GHGQ d GLGW d GQGQ d GVG[ d G[GU d G`G` d GeGY d GiG[ d GnG` d GsG` d GxGf d G}Gf d HAG^ d HFGh d HKGm d HPGi d HUGo d HZGq d H^Gk d HcGo d HhGu d HmGq d HrGm d HwGq d H{Gq d I@GU d IEFw d IJHi d IOIU d ISHO d IXGo d I]Go d IbGv d IgGq d IlGs d IpGq d IuGq d IzG~ d I?G| d JDG| d JIHG d JMHC d JRHG d JWH] d J\Hl d JaHw d JeHn d JjHe d JoHg d JtHj d JyHw d J~IH d KBIb d KGIo d KLI^ d KQIH d KVIA d KZI^ d K_JM d KdJg d KiJR d KnJ@ d KsIo d KwIs d K|I~ d LAJD d LFJE d LKJ@ d LPJG d LTI| d LYIs d L^Ib d LcJE d LhJT d LlJZ d LqIs d LvIJ d L{Ht d M@Hn d MEHa d MIHX d MNHT d MSHR d MXH_ d M]Hi d MbHj d MfHr d MkHt d MpHg d MuHj d MzHt d M~H{ d NCIC d NHIF d NMIA d NRIC d NWIF d N[IY d N`Ih d NeIY d NjIY d NoIU d NtI[ d NxI] d N}IW d OBIU d OGIL d OLIJ d OPIJ d OUID d OZID d O_IH d OdIN d OiIY d OmI` d OrIj d OwIs d O|JD d PAJM d PEJO d PJJO d POJP d PTJV d PYJe d P^Jr d PbKP d PgKm d PlLG d PqLn d PvMN d P{MJ d P?ME d QDL} d QIME d QNMn d QSON d QWPm d Q\PO d QaNe d QfMT d QkMG d QpND d QtNn d QyNX d Q~Mf d RCLt d RHLD d RMK] d RQKD d RVJn d R[J\ d R`JM d ReIw d RiIj d RnIf d RsI] d RxI` d R}Iw d SBJD d SFIw d SKIk d SPIh d SUIh d SZIj d S_J@ d ScJK d ShJP d SmJZ d SrJR d SwJM d S{JD d T@JM d TEJa d TJJt d TOJt d TTJc d TXJR d T]JM d TbIu d TgIz d TlIo d TpIx d TuIz d TzIq d T?Ix d UDIh d UIIh d UMIj d URI~ d UWJP d U\Jp d UaJw d UfKF d UjKF d UoKb d UtLa d UyLt d U~MH d VBMP d VGMa d VLMH d VQMC d VVMn d V[NK d V_Nc d VdOj d ViPp d VnO~ d VsOd d VxN? d V|Mw d WAMU d WFLr d WKLV d WPL^ d WTLt d WYLp d W^Lz d WcL{ d WhMT d WmNa d WqPx d WvS{ d W{Vm d X@W} d XEWe d XJWe d XNWl d XSWc d XXWp d X]XD d XbXo d XfXh d XkXm d XpXv d XuZq d Xz]n d X?_@ d YC^{ d YH^E d YM]n d YR]^ d YW]Z stroke m YW]Z d Y\^U d Y``P d YebK d YjcO d Yocb d YtcY d YxcZ d Y}cd d ZBcs d ZGdZ d ZLdZ d ZQbf d ZU_p d ZZ]Z d Z_[c d ZdYK d ZiVx d ZmWI d ZrXS d ZwWP d Z|UZ d [AUC d [FUC d [JSc d [ORK d [TQY d [YQf d [^Se d [cUa d [gVQ d [lVF d [qTg d [vRH d [{Pi d [?PM d \DNv d \IMN d \NKx d \SKk d \XKj d \\Kw d \aLI d \fLz d \kM_ d \pMY d \uL? d \yLO d \~L\ d ]CLa d ]HLl d ]MMT d ]QME d ]VLV d ][Kx d ]`Kj d ]eKF d ]jJ{ d ]nJ{ d ]sJj d ]xJa d ]}J@ d ^BIw d ^GI| d ^KIz d ^PIj d ^UIx d ^ZJB d ^_Iu d ^cIj d ^hIk d ^mIh d ^rI^ d ^wIb d ^|Im d _@Ij d _EIY d _JIY d _OI[ d _TId d _XIu d _]JD d _bJO d _gJV d _lJ] d _qJI d _uJB d _zJG d _?Iz d `DIz d `IIk d `NId d `RId d `WIk d `\Ij d `aIf d `fIx d `jJM d `oJX d `tJi d `yJi d `~Ji d aCJw d aGJy d aLJw d aQJv d aVJy d a[KC d a`KF d adKL d aiKN d anKN d asKD d axJr d a|Jj d bAJi d bFJ\ d bKJM d bPJ\ d bUJi d bYJw d b^KL d bcKQ d bhK[ d bmK] d brKY d bvKw d b{Ln d c@Mq d cENI d cJM{ d cOMb d cSMP d cXMY d c]NX d cbNt d cgNX d ckMs d cpMY d cuLx d czLV d c?Kw d dDK^ d dHK] d dMKf d dRKw d dWLI d d\L@ d daKf d deKD d djJa d doJg d dtKL d dyKs d d}Ks d eBKL d eGJy d eLJt d eQJZ d eUJG d eZI| d e_Iq d edId d eiI^ d enIP d esIF d ewID d e|IC d fAH? d fFIC d fKHy d fOHr d fTIJ d fYIP d f^IP d fcIY d fhI^ d flIm d fqI^ d fvIN d f{IN d g@IC d gDHr d gIHe d gNHn d gSHv d gXH} d g]H? d gaIL d gfIU d gkIh d gpJB d guIz d gzIf d g~Id d hCIU d hHIJ d hMID d hRIC d hVIH d h[H? d h`H{ d heHl d hjHT d hoHG d hsHC d hxHV d h}He d iBHn d iGHv d iLH{ d iPHr d iUHa d iZHc d i_Ha d idHT d ihHO d imHK d irHE d iwHI d i|HG d jAHG d jEHB d jJG~ d jOHE d jTHG d jYHB d j^G~ d jbHB d jgG| d jlGz d jqGv d jvGx d jzGz d j?Gq d kDGq d kIGk d kNGq d kSGi d kWGf d k\Gf d kaGf d kfGd d kkG` d koGY d ktG\ d kyG\ d k~G\ d lCG[ d lHG` d lLG^ d lQG\ d lVG^ d l[G^ d l`GS d leG[ d liGU d lnGQ d lsGY d lxGO d l}GQ stroke /Times-Roman findfont 24 scalefont setfont initmatrix -1 72 mul 300 div 1 72 mul 300 div scale -2409 88 translate 1600 3150 moveto [1 0 0 -1 0 0] concat (NOAO/IRAF sontag@barabbas.stsci.edu Tue Nov 15 15:43:21 2011 ) show showpage ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psdump_prow_256.ps�����������������������������������������������������0000644�0001750�0001750�00000061705�14203121554�020104� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-2.0 /devppi 300 def /userppi 72 def /pagewidth 8.5 def /devpixtouser { userppi mul devppi div } def /pagetolandscape 90 def /setcoords { pagewidth userppi mul 0 translate pagetolandscape rotate 1 devpixtouser 1 devpixtouser scale } def /setjoins { 1 setlinejoin 1 setlinecap } def erasepage initgraphics setcoords setjoins /getpoint { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } def /m { getpoint moveto } def /d { getpoint lineto } def 02 setlinewidth 05 setlinewidth m F\Fw d GxFw d GxGS d GxFw d IXFw d IXGS d IXFw d JyFw d JyGS d JyFw d LYFw d LYGS d LYFw d MzFw d MzGn d MzFw stroke 02 setlinewidth m MNFA d MNE\ m MOFA d MOE^ m MQFE d MQE\ m MQFE d MLE? d MIE} m MGE\ d MWE\ m MNE^ d MJE\ m MNE` d MLE\ m MQE` d MRE\ m MQE^ d MTE\ m MCEN m MrFE d MnFC d MjE} d MiEs d MiEm d MjEd d MnE^ d MrE\ d MvE\ d MzE^ d M}Ed d M?Em d M?Es d M}E} d MzFC d MvFE d MrFE m MnFA d MlE} d MjEu d MjEk d MlEd d MnE` m MzE` d M|Ed d M}Ek d M}Eu d M|E} d MzFA m MrFE d MoFC d MnE? d MlEu d MlEk d MnEb d MoE^ d MrE\ m MvE\ d MyE^ d MzEb d M|Ek d M|Eu d MzE? d MyFC d MvFE m MgEN m NWFE d NRFC d NOE} d NNEs d NNEm d NOEd d NRE^ d NWE\ d NZE\ d N_E^ d NbEd d NdEm d NdEs d NbE} d N_FC d NZFE d NWFE m NRFA d NQE} d NOEu d NOEk d NQEd d NRE` m N_E` d NaEd d NbEk d NbEu d NaE} d N_FA m NWFE d NTFC d NRE? d NQEu d NQEk d NREb d NTE^ d NWE\ m NZE\ d N]E^ d N_Eb d NaEk d NaEu d N_E? d N]FC d NZFE m NLEN stroke 05 setlinewidth m MzFw d OZFw d OZGS d OZFw d P{Fw d P{GS d P{Fw d R[Fw d R[GS d R[Fw d S{Fw d S{GS d S{Fw d U\Fw d U\Gn d U\Fw stroke 02 setlinewidth m ThE} d ThE{ d TjE{ d TjE} d ThE} m ThE? d TjE? d TkE} d TkE{ d TjEy d ThEy d TfE{ d TfE} d ThFA d TjFC d TnFE d TuFE d TyFC d T{FA d T}E} d T}Ey d T{Eu d TvEq d TnEm d TkEk d ThEg d TfEb d TfE\ m TyFA d T{E} d T{Ey d TyEu m TuFE d TxFC d TyE} d TyEy d TxEu d TuEq d TnEm m TfE` d ThEb d TkEb d TsE` d TyE` d T}Eb m TkEb d TsE^ d TyE^ d T{E` m TkEb d TsE\ d TyE\ d T{E^ d T}Eb d T}Ef m TeEN m UUFE d UPFC d UME} d UKEs d UKEm d UMEd d UPE^ d UUE\ d UXE\ d U]E^ d U`Ed d UaEm d UaEs d U`E} d U]FC d UXFE d UUFE m UPFA d UNE} d UMEu d UMEk d UNEd d UPE` m U]E` d U^Ed d U`Ek d U`Eu d U^E} d U]FA m UUFE d UQFC d UPE? d UNEu d UNEk d UPEb d UQE^ d UUE\ m UXE\ d U[E^ d U]Eb d U^Ek d U^Eu d U]E? d U[FC d UXFE m UIEN m UyFE d UtFC d UqE} d UpEs d UpEm d UqEd d UtE^ d UyE\ d U|E\ d VAE^ d VDEd d VFEm d VFEs d VDE} d VAFC d U|FE d UyFE m UtFA d UsE} d UqEu d UqEk d UsEd d UtE` m VAE` d VCEd d VDEk d VDEu d VCE} d VAFA m UyFE d UvFC d UtE? d UsEu d UsEk d UtEb d UvE^ d UyE\ m U|E\ d V@E^ d VAEb d VCEk d VCEu d VAE? d V@FC d U|FE m UnEN stroke 05 setlinewidth m U\Fw d V|Fw d V|GS d V|Fw d X]Fw d X]GS d X]Fw d Y}Fw d Y}GS d Y}Fw d [^Fw d [^GS d [^Fw d \~Fw d \~Gn d \~Fw stroke 02 setlinewidth m \JE} d \JE{ d \LE{ d \LE} d \JE} m \JE? d \LE? d \ME} d \ME{ d \LEy d \JEy d \IE{ d \IE} d \JFA d \LFC d \QFE d \WFE d \\FC d \]E? d \]Ey d \\Eu d \WEs m \ZFC d \\E? d \\Ey d \ZEu m \UFE d \YFC d \ZE? d \ZEy d \YEu d \UEs m \REs d \WEs d \ZEq d \]Em d \_Ei d \_Ed d \]E` d \\E^ d \WE\ d \QE\ d \LE^ d \JE` d \IEd d \IEf d \JEg d \LEg d \MEf d \MEd d \LEb d \JEb m \\Em d \]Ei d \]Ed d \\E` m \UEs d \YEq d \ZEo d \\Ei d \\Ed d \ZE^ d \WE\ m \JEf d \JEd d \LEd d \LEf d \JEf m \GEN m \wFE d \rFC d \oE} d \mEs d \mEm d \oEd d \rE^ d \wE\ d \zE\ d \?E^ d ]BEd d ]DEm d ]DEs d ]BE} d \?FC d \zFE d \wFE m \rFA d \qE} d \oEu d \oEk d \qEd d \rE` m \?E` d ]@Ed d ]BEk d ]BEu d ]@E} d \?FA m \wFE d \tFC d \rE? d \qEu d \qEk d \rEb d \tE^ d \wE\ m \zE\ d \}E^ d \?Eb d ]@Ek d ]@Eu d \?E? d \}FC d \zFE m \lEN m ]\FE d ]WFC d ]TE} d ]REs d ]REm d ]TEd d ]WE^ d ]\E\ d ]_E\ d ]dE^ d ]gEd d ]hEm d ]hEs d ]gE} d ]dFC d ]_FE d ]\FE m ]WFA d ]UE} d ]TEu d ]TEk d ]UEd d ]WE` m ]dE` d ]eEd d ]gEk d ]gEu d ]eE} d ]dFA m ]\FE d ]XFC d ]WE? d ]UEu d ]UEk d ]WEb d ]XE^ d ]\E\ m ]_E\ d ]bE^ d ]dEb d ]eEk d ]eEu d ]dE? d ]bFC d ]_FE m ]QEN stroke 05 setlinewidth m \~Fw d ^_Fw d ^_GS d ^_Fw d _?Fw d _?GS d _?Fw d a`Fw d a`GS d a`Fw d c@Fw d c@GS d c@Fw d daFw d daGn d daFw stroke 02 setlinewidth m cxE? d cxE\ m cyFA d cyE^ m c{FE d c{E\ m c{FE d ciEg d dCEg m csE\ d d@E\ m cxE^ d ctE\ m cxE` d cvE\ m c{E` d c|E\ m c{E^ d c~E\ m ciEN m dYFE d dTFC d dQE} d dPEs d dPEm d dQEd d dTE^ d dYE\ d d\E\ d daE^ d ddEd d dfEm d dfEs d ddE} d daFC d d\FE d dYFE m dTFA d dSE} d dQEu d dQEk d dSEd d dTE` m daE` d dcEd d ddEk d ddEu d dcE} d daFA m dYFE d dVFC d dTE? d dSEu d dSEk d dTEb d dVE^ d dYE\ m d\E\ d d_E^ d daEb d dcEk d dcEu d daE? d d_FC d d\FE m dNEN m d~FE d dyFC d dvE} d dtEs d dtEm d dvEd d dyE^ d d~E\ d eAE\ d eFE^ d eIEd d eKEm d eKEs d eIE} d eFFC d eAFE d d~FE m dyFA d dxE} d dvEu d dvEk d dxEd d dyE` m eFE` d eGEd d eIEk d eIEu d eGE} d eFFA m d~FE d d{FC d dyE? d dxEu d dxEk d dyEb d d{E^ d d~E\ m eAE\ d eDE^ d eFEb d eGEk d eGEu d eFE? d eDFC d eAFE m dsEN stroke 05 setlinewidth m daFw d fAFw d fAGS d fAFw d gaFw d gaGS d gaFw d iBFw d iBGS d iBFw d jbFw d jbGS d jbFw d lCFw d lCGn d lCFw stroke 02 setlinewidth m kPFE d kMEq d kPEu d kUEw d kZEw d k_Eu d kbEq d kcEk d kcEg d kbEb d k_E^ d kZE\ d kUE\ d kPE^ d kOE` d kMEd d kMEf d kOEg d kPEg d kREf d kREd d kPEb d kOEb m k`Eq d kbEm d kbEf d k`Eb m kZEw d k]Eu d k_Es d k`Em d k`Ef d k_E` d k]E^ d kZE\ m kOEf d kOEd d kPEd d kPEf d kOEf m kPFE d k`FE m kPFC d k]FC m kPFA d kWFA d k]FC d k`FE m kLEN m k{FE d kwFC d ktE} d krEs d krEm d ktEd d kwE^ d k{E\ d k?E\ d lCE^ d lGEd d lHEm d lHEs d lGE} d lCFC d k?FE d k{FE m kwFA d kuE} d ktEu d ktEk d kuEd d kwE` m lCE` d lEEd d lGEk d lGEu d lEE} d lCFA m k{FE d kxFC d kwE? d kuEu d kuEk d kwEb d kxE^ d k{E\ m k?E\ d lBE^ d lCEb d lEEk d lEEu d lCE? d lBFC d k?FE m kpEN m l`FE d l[FC d lXE} d lWEs d lWEm d lXEd d l[E^ d l`E\ d lcE\ d lhE^ d lkEd d lmEm d lmEs d lkE} d lhFC d lcFE d l`FE m l[FA d lZE} d lXEu d lXEk d lZEd d l[E` m lhE` d ljEd d lkEk d lkEu d ljE} d lhFA m l`FE d l]FC d l[E? d lZEu d lZEk d l[Eb d l]E^ d l`E\ m lcE\ d lfE^ d lhEb d ljEk d ljEu d lhE? d lfFC d lcFE m lUEN stroke 05 setlinewidth m lCFw d l}Fw m l}Fw d l}Ga d laGa d l}Ga d l}Hi d laHi d l}Hi d l}Ir d laIr d l}Ir d l}Jz d laJz d l}Jz d l}LC d lELC d l}LC d l}ML d laML d l}ML d l}NT d laNT d l}NT d l}O] d laO] d l}O] d l}Pe d laPe d l}Pe d l}Qn d lEQn d l}Qn d l}Rw d laRw d l}Rw d l}S? d laS? d l}S? d l}UH d laUH d l}UH d l}VP d laVP d l}VP d l}WY d lEWY d l}WY d l}Xa d laXa d l}Xa d l}Yj d laYj d l}Yj d l}Zs d laZs d l}Zs d l}[{ d la[{ d l}[{ d l}]D d lE]D d l}]D d l}^L d la^L d l}^L d l}_U d la_U d l}_U d l}`^ d la`^ d l}`^ d l}af d laaf d l}af d l}bo d lEbo d l}bo d l}cw d lacw d l}cw d l}dZ m F\Fw d F\Ga d FxGa d F\Ga d F\Hi d FxHi d F\Hi d F\Ir d FxIr d F\Ir d F\Jz d FxJz d F\Jz d F\LC d GTLC d F\LC stroke 02 setlinewidth m D_LV d D\LC d D_LG d DdLI d DhLI d DmLG d DpLC d DrK} d DrKy d DpKs d DmKp d DhKn d DdKn d D_Kp d D]Kq d D\Ku d D\Kw d D]Ky d D_Ky d D`Kw d D`Ku d D_Ks d D]Ks m DoLC d DpK? d DpKw d DoKs m DhLI d DlLG d DmLE d DoK? d DoKw d DmKq d DlKp d DhKn m D]Kw d D]Ku d D_Ku d D_Kw d D]Kw m D_LV d DoLV m D_LT d DlLT m D_LR d DeLR d DlLT d DoLV m DZK` m EJLV d EELT d EBLO d E@LE d E@K? d EBKu d EEKp d EJKn d EMKn d ERKp d EUKu d EWK? d EWLE d EULO d ERLT d EMLV d EJLV m EELR d EDLO d EBLG d EBK} d EDKu d EEKq m ERKq d ESKu d EUK} d EULG d ESLO d ERLR m EJLV d EGLT d EELQ d EDLG d EDK} d EEKs d EGKp d EJKn m EMKn d EPKp d ERKs d ESK} d ESLG d ERLQ d EPLT d EMLV m D?K` m EoLV d EjLT d EgLO d EeLE d EeK? d EgKu d EjKp d EoKn d ErKn d EwKp d EzKu d E{K? d E{LE d EzLO d EwLT d ErLV d EoLV m EjLR d EhLO d EgLG d EgK} d EhKu d EjKq m EwKq d ExKu d EzK} d EzLG d ExLO d EwLR m EoLV d EkLT d EjLQ d EhLG d EhK} d EjKs d EkKp d EoKn m ErKn d EuKp d EwKs d ExK} d ExLG d EwLQ d EuLT d ErLV m EdK` stroke 05 setlinewidth m F\LC d F\ML d FxML d F\ML d F\NT d FxNT d F\NT d F\O] d FxO] d F\O] d F\Pe d FxPe d F\Pe d F\Qn d GTQn d F\Qn stroke 02 setlinewidth m DAQ} d DAQY m DBQ} d DBQ[ m DDRA d DDQY m DDRA d C?Q{ d C|Qz m CzQY d DJQY m DAQ[ d C}QY m DAQ\ d C?QY m DDQ\ d DEQY m DDQ[ d DGQY m CuQK m DeRA d D`Q? d D]Qz d D\Qp d D\Qj d D]Q` d D`Q[ d DeQY d DhQY d DmQ[ d DpQ` d DrQj d DrQp d DpQz d DmQ? d DhRA d DeRA m D`Q} d D_Qz d D]Qr d D]Qh d D_Q` d D`Q\ m DmQ\ d DoQ` d DpQh d DpQr d DoQz d DmQ} m DeRA d DbQ? d D`Q{ d D_Qr d D_Qh d D`Q^ d DbQ[ d DeQY m DhQY d DlQ[ d DmQ^ d DoQh d DoQr d DmQ{ d DlQ? d DhRA m DZQK m EJRA d EEQ? d EBQz d E@Qp d E@Qj d EBQ` d EEQ[ d EJQY d EMQY d ERQ[ d EUQ` d EWQj d EWQp d EUQz d ERQ? d EMRA d EJRA m EEQ} d EDQz d EBQr d EBQh d EDQ` d EEQ\ m ERQ\ d ESQ` d EUQh d EUQr d ESQz d ERQ} m EJRA d EGQ? d EEQ{ d EDQr d EDQh d EEQ^ d EGQ[ d EJQY m EMQY d EPQ[ d ERQ^ d ESQh d ESQr d ERQ{ d EPQ? d EMRA m D?QK m EoRA d EjQ? d EgQz d EeQp d EeQj d EgQ` d EjQ[ d EoQY d ErQY d EwQ[ d EzQ` d E{Qj d E{Qp d EzQz d EwQ? d ErRA d EoRA m EjQ} d EhQz d EgQr d EgQh d EhQ` d EjQ\ m EwQ\ d ExQ` d EzQh d EzQr d ExQz d EwQ} m EoRA d EkQ? d EjQ{ d EhQr d EhQh d EjQ^ d EkQ[ d EoQY m ErQY d EuQ[ d EwQ^ d ExQh d ExQr d EwQ{ d EuQ? d ErRA m EdQK stroke 05 setlinewidth m F\Qn d F\Rw d FxRw d F\Rw d F\S? d FxS? d F\S? d F\UH d FxUH d F\UH d F\VP d FxVP d F\VP d F\WY d GTWY d F\WY stroke 02 setlinewidth m DAWh d DAWD m DBWh d DBWE m DDWl d DDWD m DDWl d C?Wf d C|We m CzWD d DJWD m DAWE d C}WD m DAWG d C?WD m DDWG d DEWD m DDWE d DGWD m CuVv m D_Wl d D\WY d D_W] d DdW_ d DhW_ d DmW] d DpWY d DrWS d DrWO d DpWI d DmWE d DhWD d DdWD d D_WE d D]WG d D\WK d D\WM d D]WO d D_WO d D`WM d D`WK d D_WI d D]WI m DoWY d DpWU d DpWM d DoWI m DhW_ d DlW] d DmW[ d DoWU d DoWM d DmWG d DlWE d DhWD m D]WM d D]WK d D_WK d D_WM d D]WM m D_Wl d DoWl m D_Wj d DlWj m D_Wh d DeWh d DlWj d DoWl m DZVv m EJWl d EEWj d EBWe d E@W[ d E@WU d EBWK d EEWE d EJWD d EMWD d ERWE d EUWK d EWWU d EWW[ d EUWe d ERWj d EMWl d EJWl m EEWh d EDWe d EBW] d EBWS d EDWK d EEWG m ERWG d ESWK d EUWS d EUW] d ESWe d ERWh m EJWl d EGWj d EEWf d EDW] d EDWS d EEWI d EGWE d EJWD m EMWD d EPWE d ERWI d ESWS d ESW] d ERWf d EPWj d EMWl m D?Vv m EoWl d EjWj d EgWe d EeW[ d EeWU d EgWK d EjWE d EoWD d ErWD d EwWE d EzWK d E{WU d E{W[ d EzWe d EwWj d ErWl d EoWl m EjWh d EhWe d EgW] d EgWS d EhWK d EjWG m EwWG d ExWK d EzWS d EzW] d ExWe d EwWh m EoWl d EkWj d EjWf d EhW] d EhWS d EjWI d EkWE d EoWD m ErWD d EuWE d EwWI d ExWS d ExW] d EwWf d EuWj d ErWl m EdVv stroke 05 setlinewidth m F\WY d F\Xa d FxXa d F\Xa d F\Yj d FxYj d F\Yj d F\Zs d FxZs d F\Zs d F\[{ d Fx[{ d F\[{ d F\]D d GT]D d F\]D stroke 02 setlinewidth m Cy]P d Cy]N d Cz]N d Cz]P d Cy]P m Cy]Q d Cz]Q d C|]P d C|]N d Cz]L d Cy]L d Cw]N d Cw]P d Cy]S d Cz]U d C?]W d DE]W d DJ]U d DL]S d DM]P d DM]L d DL]H d DG]D d C?]@ d C|\~ d Cy\z d Cw\t d Cw\n m DJ]S d DL]P d DL]L d DJ]H m DE]W d DI]U d DJ]P d DJ]L d DI]H d DE]D d C?]@ m Cw\r d Cy\t d C|\t d DD\r d DJ\r d DM\t m C|\t d DD\p d DJ\p d DL\r m C|\t d DD\n d DJ\n d DL\p d DM\t d DM\x m Cu\a m De]W d D`]U d D]]P d D\]F d D\]@ d D]\v d D`\p d De\n d Dh\n d Dm\p d Dp\v d Dr]@ d Dr]F d Dp]P d Dm]U d Dh]W d De]W m D`]S d D_]P d D]]H d D]\~ d D_\v d D`\r m Dm\r d Do\v d Dp\~ d Dp]H d Do]P d Dm]S m De]W d Db]U d D`]Q d D_]H d D_\~ d D`\t d Db\p d De\n m Dh\n d Dl\p d Dm\t d Do\~ d Do]H d Dm]Q d Dl]U d Dh]W m DZ\a m EJ]W d EE]U d EB]P d E@]F d E@]@ d EB\v d EE\p d EJ\n d EM\n d ER\p d EU\v d EW]@ d EW]F d EU]P d ER]U d EM]W d EJ]W m EE]S d ED]P d EB]H d EB\~ d ED\v d EE\r m ER\r d ES\v d EU\~ d EU]H d ES]P d ER]S m EJ]W d EG]U d EE]Q d ED]H d ED\~ d EE\t d EG\p d EJ\n m EM\n d EP\p d ER\t d ES\~ d ES]H d ER]Q d EP]U d EM]W m D?\a m Eo]W d Ej]U d Eg]P d Ee]F d Ee]@ d Eg\v d Ej\p d Eo\n d Er\n d Ew\p d Ez\v d E{]@ d E{]F d Ez]P d Ew]U d Er]W d Eo]W m Ej]S d Eh]P d Eg]H d Eg\~ d Eh\v d Ej\r m Ew\r d Ex\v d Ez\~ d Ez]H d Ex]P d Ew]S m Eo]W d Ek]U d Ej]Q d Eh]H d Eh\~ d Ej\t d Ek\p d Eo\n m Er\n d Eu\p d Ew\t d Ex\~ d Ex]H d Ew]Q d Eu]U d Er]W m Ed\a stroke 05 setlinewidth m F\]D d F\^L d Fx^L d F\^L d F\_U d Fx_U d F\_U d F\`^ d Fx`^ d F\`^ d F\af d Fxaf d F\af d F\bo d GTbo d F\bo stroke 02 setlinewidth m Cybz d Cybx d Czbx d Czbz d Cybz m Cyb| d Czb| d C|bz d C|bx d Czbw d Cybw d Cwbx d Cwbz d Cyb~ d Czc@ d C?cB d DEcB d DJc@ d DLb~ d DMbz d DMbw d DLbs d DGbo d C?bk d C|bi d Cybe d Cwb_ d CwbY m DJb~ d DLbz d DLbw d DJbs m DEcB d DIc@ d DJbz d DJbw d DIbs d DEbo d C?bk m Cwb] d Cyb_ d C|b_ d DDb] d DJb] d DMb_ m C|b_ d DDb[ d DJb[ d DLb] m C|b_ d DDbY d DJbY d DLb[ d DMb_ d DMbc m CubL m D_cB d D\bo d D_bs d Ddbu d Dhbu d Dmbs d Dpbo d Drbi d Drbe d Dpb_ d Dmb[ d DhbY d DdbY d D_b[ d D]b] d D\ba d D\bc d D]be d D_be d D`bc d D`ba d D_b_ d D]b_ m Dobo d Dpbk d Dpbc d Dob_ m Dhbu d Dlbs d Dmbq d Dobk d Dobc d Dmb] d Dlb[ d DhbY m D]bc d D]ba d D_ba d D_bc d D]bc m D_cB d DocB m D_c@ d Dlc@ m D_b~ d Deb~ d Dlc@ d DocB m DZbL m EJcB d EEc@ d EBbz d E@bq d E@bk d EBba d EEb[ d EJbY d EMbY d ERb[ d EUba d EWbk d EWbq d EUbz d ERc@ d EMcB d EJcB m EEb~ d EDbz d EBbs d EBbi d EDba d EEb] m ERb] d ESba d EUbi d EUbs d ESbz d ERb~ m EJcB d EGc@ d EEb| d EDbs d EDbi d EEb_ d EGb[ d EJbY m EMbY d EPb[ d ERb_ d ESbi d ESbs d ERb| d EPc@ d EMcB m D?bL m EocB d Ejc@ d Egbz d Eebq d Eebk d Egba d Ejb[ d EobY d ErbY d Ewb[ d Ezba d E{bk d E{bq d Ezbz d Ewc@ d ErcB d EocB m Ejb~ d Ehbz d Egbs d Egbi d Ehba d Ejb] m Ewb] d Exba d Ezbi d Ezbs d Exbz d Ewb~ m EocB d Ekc@ d Ejb| d Ehbs d Ehbi d Ejb_ d Ekb[ d EobY m ErbY d Eub[ d Ewb_ d Exbi d Exbs d Ewb| d Euc@ d ErcB m EdbL stroke 05 setlinewidth m F\bo d F\cw d Fxcw d F\cw d F\dZ m F\dZ d GxdZ d Gxc~ d GxdZ d IXdZ d IXc~ d IXdZ d JydZ d Jyc~ d JydZ d LYdZ d LYc~ d LYdZ d MzdZ d Mzcb d MzdZ d OZdZ d OZc~ d OZdZ d P{dZ d P{c~ d P{dZ d R[dZ d R[c~ d R[dZ d S{dZ d S{c~ d S{dZ d U\dZ d U\cb d U\dZ d V|dZ d V|c~ d V|dZ d X]dZ d X]c~ d X]dZ d Y}dZ d Y}c~ d Y}dZ d [^dZ d [^c~ d [^dZ d \~dZ d \~cb d \~dZ d ^_dZ d ^_c~ d ^_dZ d _?dZ d _?c~ d _?dZ d a`dZ d a`c~ d a`dZ d c@dZ d c@c~ d c@dZ d dadZ d dacb d dadZ d fAdZ d fAc~ d fAdZ d gadZ d gac~ d gadZ d iBdZ d iBc~ d iBdZ d jbdZ d jbc~ d jbdZ d lCdZ d lCcb d lCdZ d l}dZ stroke 02 setlinewidth m VEDV d VGD\ d VGDP d VEDV d VBDZ d U?D\ d U{D\ d UvDZ d UsDV d UrDR d UpDL d UpDC d UrC} d UsCy d UvCu d U{Cs d U?Cs d VBCu d VECy d VGC} m UuDV d UsDR d UrDL d UrDC d UsC} d UuCy m U{D\ d UxDZ d UuDT d UsDL d UsDC d UuC{ d UxCu d U{Cs m UoCe m V`DN d V[DL d VXDH d VVDC d VVC? d VXCy d V[Cu d V`Cs d VcCs d VhCu d VkCy d VlC? d VlDC d VkDH d VhDL d VcDN d V`DN m VYDH d VXDD d VXC} d VYCy m ViCy d VkC} d VkDD d ViDH m V`DN d V]DL d V[DJ d VYDD d VYC} d V[Cw d V]Cu d V`Cs m VcCs d VfCu d VhCw d ViC} d ViDD d VhDJ d VfDL d VcDN m VUCe m W@D\ d W@Cs m WCDZ d WCCu m VyD\ d WED\ d WECs m VyCs d WLCs m V|D\ d W@DZ m V~D\ d W@DX m W@Cu d V|Cs m W@Cw d V~Cs m WECw d WGCs m WECu d WJCs m VyCe m WWDN d WWC} d WXCw d WYCu d W\Cs d WaCs d WcCu d WeCw d WfC{ m WXDL d WXC{ d WYCw m WRDN d WYDN d WYC{ d W[Cu d W\Cs m WfDN d WfCs d WmCs m WhDL d WhCu m WbDN d WiDN d WiCs m WTDN d WWDL m WUDN d WWDJ m WiCw d WjCs m WiCu d WlCs m WRCe m W?DN d W?Cs m X@DL d X@Cu m W{DN d XBDN d XBCs m XBDF d XCDJ d XDDL d XGDN d XJDN d XMDL d XNDJ d XODD d XOCs m XMDJ d XNDD d XNCu m XJDN d XLDL d XMDF d XMCs m XODF d XQDJ d XRDL d XTDN d XXDN d XZDL d X\DJ d X]DD d X]Cs m XZDJ d X\DD d X\Cu m XXDN d XYDL d XZDF d XZCs m W{Cs d XECs m XICs d XSCs m XWCs d XaCs m W}DN d W?DL m W~DN d W?DJ m W?Cu d W}Cs m W?Cw d W~Cs m XBCw d XCCs m XBCu d XDCs m XMCu d XJCs m XMCw d XLCs m XOCw d XQCs m XOCu d XRCs m XZCu d XXCs m XZCw d XYCs m X]Cw d X^Cs m X]Cu d X_Cs m W{Ce m XoDN d XoCs m XpDL d XpCu m XjDN d XrDN d XrCs m XrDF d XsDJ d XtDL d XwDN d X{DN d X~DL d Y@DJ d YADD d YACs m X~DJ d Y@DD d Y@Cu m X{DN d X}DL d X~DF d X~Cs m XjCs d XvCs m XzCs d YECs m XlDN d XoDL m XmDN d XoDJ m XoCu d XlCs m XoCw d XmCs m XrCw d XsCs m XrCu d XtCs m X~Cu d X{Cs m X~Cw d X}Cs m YACw d YCCs m YACu d YDCs m XjCe m YTDi m YTCe m ZCDd d Y?D` d Y{DZ d YwDR d YuDH d YuDA d YwCw d Y{Co d Y?Ci d ZCCe m Y{DX d YyDR d YwDJ d YwC? d YyCw d Y{Cq m Y?D` d Y}D\ d Y{DV d YyDJ d YyC? d Y{Cs d Y}Cm d Y?Ci m YrCe m ZSDN d ZSCe m ZTDL d ZTCg m ZNDN d ZVDN d ZVCe m ZVDH d ZWDL d ZZDN d Z^DN d ZbDL d ZeDH d ZgDC d ZgC? d ZeCy d ZbCu d Z^Cs d ZZCs d ZWCu d ZVCy m ZdDH d ZeDD d ZeC} d ZdCy m Z^DN d ZaDL d ZbDJ d ZdDD d ZdC} d ZbCw d ZaCu d Z^Cs m ZNCe d ZZCe m ZPDN d ZSDL m ZQDN d ZSDJ m ZSCg d ZPCe m ZSCi d ZQCe m ZVCi d ZWCe m ZVCg d ZYCe m ZNCe m Z{D\ d Z{DX d [@DX d [@D\ d Z{D\ m Z~D\ d Z~DX m Z{DZ d [@DZ m Z{DN d Z{Cs m Z~DL d Z~Cu m ZtDN d [@DN d [@Cs m ZtCs d [GCs m ZwDN d Z{DL m ZyDN d Z{DJ m Z{Cu d ZwCs m Z{Cw d ZyCs m [@Cw d [BCs m [@Cu d [ECs m ZtCe m [QDN d [`Cs m [RDN d [bCs m [TDN d [dCs m [bDL d [RCu m [MDN d [XDN m []DN d [gDN m [MCs d [WCs m [\Cs d [gCs m [ODN d [RDL m [WDN d [TDL m [_DN d [bDL m [eDN d [bDL m [RCu d [OCs m [RCu d [UCs m [`Cu d []Cs m [bCu d [eCs m [MCe m [wDC d \IDC d \IDF d \HDJ d \FDL d \ADN d [~DN d [yDL d [uDH d [tDC d [tC? d [uCy d [yCu d [~Cs d \ACs d \FCu d \ICy m \HDD d \HDF d \FDJ m [wDH d [uDD d [uC} d [wCy m \FDC d \FDH d \DDL d \ADN m [~DN d [zDL d [yDJ d [wDD d [wC} d [yCw d [zCu d [~Cs m [rCe m \\D\ d \\Cs m \_DZ d \_Cu m \UD\ d \aD\ d \aCs m \UCs d \hCs m \XD\ d \\DZ m \ZD\ d \\DX m \\Cu d \XCs m \\Cw d \ZCs m \aCw d \cCs m \aCu d \fCs m \UCe m ]BDJ d ]DDN d ]DDF d ]BDJ d ]@DL d \}DN d \uDN d \rDL d \pDJ d \pDF d \rDC d \uDA d \~C? d ]BC} d ]DCw m \rDL d \pDF m \rDD d \uDC d \~DA d ]BC? m ]DC} d ]BCu m \pDJ d \rDF d \uDD d \~DC d ]BDA d ]DC} d ]DCw d ]BCu d \~Cs d \wCs d \tCu d \rCw d \pC{ d \pCs d \rCw m \nCe m ]SDd d ]WD` d ][DZ d ]_DR d ]aDH d ]aDA d ]_Cw d ][Co d ]WCi d ]SCe m ][DX d ]]DR d ]_DJ d ]_C? d ]]Cw d ][Cq m ]WD` d ]YD\ d ][DV d ]]DJ d ]]C? d ][Cs d ]YCm d ]WCi m ]OCe m Ukes d UkeJ d UseJ m Umeq d UmeL m Uges d Uoes d UoeJ m Uke_ d Ujec d Ugee d Udee d U_ec d U\e_ d U[eZ d U[eV d U\eP d U_eL d UdeJ d UgeJ d UjeL d UkeP m U^e_ d U\e\ d U\eT d U^eP m Udee d Uaec d U_ea d U^e\ d U^eT d U_eN d UaeL d UdeJ m Uhes d Ukeq m Ujes d Ukeo m UoeN d UpeJ m UoeL d UreJ m UYd| m VDeZ d VVeZ d VVe] d VUea d VSec d VNee d VKee d VFec d VCe_ d VAeZ d VAeV d VCeP d VFeL d VKeJ d VNeJ d VSeL d VVeP m VUe\ d VUe] d VSea m VDe_ d VCe\ d VCeT d VDeP m VSeZ d VSe_ d VRec d VNee m VKee d VHec d VFea d VDe\ d VDeT d VFeN d VHeL d VKeJ m U?d| m Vdee d VoeJ m Vfee d VoeN m Vhee d VpeN m Vyec d VpeN d VoeJ m Vcee d Vmee m Vree d V|ee m Vcee d Vhea m Vkee d Vhec m Vvee d Vyec m V{ee d Vyec m Vcd| m WNe{ d WNeB m WTe{ d WTeB m W[ei d W[ek d WYek d WYeg d W\eg d W\ek d W[eo d WYeq d WTes d WNes d WIeq d WFem d WFeg d WGec d WLe_ d WVe\ d WYeZ d W[eV d W[eP d WYeL m WGeg d WIec d WLea d WVe] d WYe\ d W[eX m WIeq d WGem d WGei d WIee d WLec d WVe_ d W[e\ d W\eX d W\eR d W[eN d WYeL d WTeJ d WNeJ d WIeL d WGeN d WFeR d WFeV d WIeV d WIeR d WGeR d WGeT m WDd| m Wnee d Wnd| m Woec d Wod~ m Wiee d Wqee d Wqd| m Wqe_ d Wrec d Wuee d Wxee d W}ec d X@e_ d XAeZ d XAeV d X@eP d W}eL d WxeJ d WueJ d WreL d WqeP m W~e_ d X@e\ d X@eT d W~eP m Wxee d W{ec d W}ea d W~e\ d W~eT d W}eN d W{eL d WxeJ m Wid| d Wud| m Wkee d Wnec m Wlee d Wnea m Wnd~ d Wkd| m Wne@ d Wld| stroke m Wld| m Wqe@ d Wrd| m Wqd~ d Wtd| m Wid| m XVes d XVeo d X[eo d X[es d XVes m XYes d XYeo m XVeq d X[eq m XVee d XVeJ m XYec d XYeL m XOee d X[ee d X[eJ m XOeJ d XbeJ m XRee d XVec m XTee d XVea m XVeL d XReJ m XVeN d XTeJ m X[eN d X]eJ m X[eL d X_eJ m XOd| m Xkee d X{eJ m Xmee d X}eJ m Xnee d X~eJ m X}ec d XmeL m Xhee d Xsee m Xxee d YAee m XheJ d XreJ m XveJ d YAeJ m Xjee d Xmec m Xree d Xnec m Xzee d X}ec m Y@ee d X}ec m XmeL d XjeJ m XmeL d XpeJ m X{eL d XxeJ m X}eL d Y@eJ m Xhd| m YTee d YRec d YRea d YTe_ d YWe_ d YYea d YYec d YWee d YTee m YTec d YTea d YWea d YWec d YTec m YTeP d YReN d YReL d YTeJ d YWeJ d YYeL d YYeN d YWeP d YTeP m YTeN d YTeL d YWeL d YWeN d YTeN m YMd| m Ydf@ m Ydd| m ZHee d ZHeJ m ZJec d ZJeL m ZCee d ZLee d ZLeJ m ZYea d ZYec d ZWec d ZWe_ d Z[e_ d Z[ec d ZYee d ZUee d ZRec d ZNe_ d ZLeZ m ZCeJ d ZReJ m ZEee d ZHec m ZGee d ZHea m ZHeL d ZEeJ m ZHeN d ZGeJ m ZLeN d ZNeJ m ZLeL d ZPeJ m ZCd| m Zoee d Zjec d Zge_ d ZeeZ d ZeeV d ZgeP d ZjeL d ZoeJ d ZreJ d ZveL d ZzeP d Z{eV d Z{eZ d Zze_ d Zvec d Zree d Zoee m Zhe_ d Zge\ d ZgeT d ZheP m ZxeP d ZzeT d Zze\ d Zxe_ m Zoee d Zkec d Zjea d Zhe\ d ZheT d ZjeN d ZkeL d ZoeJ m ZreJ d ZueL d ZveN d ZxeT d Zxe\ d Zvea d Zuec d Zree m Zcd| m [Kee d [PeJ m [Lee d [PeP m [Nee d [ReP m [Vee d [ReP d [PeJ m [Vee d [[eJ m [Wee d [[eP m [Vee d [Yee d []eP m [aec d []eP d [[eJ m [Hee d [Ree m []ee d [eee m [Hee d [Lec m [Pee d [Nec m [^ee d [aec m [dee d [aec m [Hd| m [sf@ m [sd| m \Uek d \Uei d \Vei d \Vek d \Uek m \Uem d \Vem d \Xek d \Xei d \Veg d \Ueg d \Sei d \Sek d \Ueo d \Veq d \[es d \aes d \feq d \heo d \iek d \ieg d \hec d \ce_ d \[e\ d \XeZ d \UeV d \SeP d \SeJ m \feo d \hek d \heg d \fec m \aes d \eeq d \fek d \feg d \eec d \ae_ d \[e\ m \SeN d \UeP d \XeP d \`eN d \feN d \ieP m \XeP d \`eL d \feL d \heN m \XeP d \`eJ d \feJ d \heL d \ieP d \ieT m \Rd| m \{es d \xe_ d \{ec d ]@ee d ]Eee d ]Iec d ]Me_ d ]NeZ d ]NeV d ]MeP d ]IeL d ]EeJ d ]@eJ d \{eL d \yeN d \xeR d \xeT d \yeV d \{eV d \}eT d \}eR d \{eP d \yeP m ]Ke_ d ]Me\ d ]MeT d ]KeP m ]Eee d ]Hec d ]Iea d ]Ke\ d ]KeT d ]IeN d ]HeL d ]EeJ m \yeT d \yeR d \{eR d \{eT d \yeT m \{es d ]Kes m \{eq d ]Heq m \{eo d ]Aeo d ]Heq d ]Kes m \vd| m ]nem d ]nek d ]pek d ]pem d ]nem m ]peo d ]neo d ]lem d ]lek d ]nei d ]pei d ]qek d ]qem d ]peq d ]les d ]hes d ]ceq d ]`em d ]^ei d ]]ea d ]]eV d ]^eP d ]aeL d ]feJ d ]ieJ d ]neL d ]qeP d ]seV d ]seX d ]qe] d ]nea d ]iec d ]fec d ]cea d ]ae_ d ]`e\ m ]aem d ]`ei d ]^ea d ]^eV d ]`eP d ]aeN m ]peP d ]qeT d ]qeZ d ]pe] m ]hes d ]eeq d ]ceo d ]aek d ]`ec d ]`eV d ]aeP d ]ceL d ]feJ m ]ieJ d ]leL d ]neN d ]peT d ]peZ d ]ne_ d ]lea d ]iec m ][d| m F\F{ d FaF| d FfF| d FkF| d FoF{ d FtF| d FyFy d F~F~ d GCF| d GHFy d GLF| d GQF| d GVFz d G[F~ d G`F| d GeF} d GiF~ d GnGA d GsF? d GxGA d G}GD d HAGA d HFGB d HKGA d HPGD d HUGA d HZGC d H^GD d HcGE d HhGC d HmGE d HrGD d HwGC d H{GE d I@GF d IEGD d IJGF d IOGH d ISGE d IXGD d I]GF d IbGG d IgGD d IlGE d IpGI d IuGM d IzGL d I?GN d JDGN d JIGL d JMGN d JRGQ d JWGN d J\GO d JaGU d JeGT d JjGV d JoGY d JtGY d JyG^ d J~Gc d KBGd d KGGg d KLGn d KQGj d KVGf d KZGb d K_G` d KdG` d KiGd d KnGk d KsGt d KwGq d K|Gn d LAGl d LFGp d LKGn d LPGh d LTGd d LYGf d L^Gf d LcGf d LhGb d LlGc d LqGc d LvGe d L{Gc d M@G` d MEG` d MIG] d MNGY d MSGZ d MXG[ d M]GW d MbG\ d MfG] d MkG_ d MpG\ d MuG` d MzG^ d M~G^ d NCGd d NHGk d NMGi d NRGn d NWGi d N[Gm d N`Gm d NeGl d NjGi d NoGj d NtGi d NxGh d N}Gf d OBGi d OGGn d OLGo d OPGo d OUGn d OZGu d O_Gu d OdGz d OiGy d OmGu d OrGv d OwGq d O|Gt d PAGr d PEGq d PJGw d POG} d PTHD d PYHM d P^HS d PbH_ d PgHg d PlHg d PqHi d PvHn d P{Ht d P?Hu d QDHy d QIH| d QNIC d QSIQ d QWIX d Q\IP d QaIC d QfHt d QkHg d QpH] d QtHV d QyHS d Q~HL d RCHE d RHG? d RMG~ d RQG? d RVG} d R[G~ d R`G| d ReGy d RiGz d RnGy d RsGq d RxGq d R}Gq d SBGt d SFGv d SKGu d SPGx d SUG{ d SZGu d S_Gy d ScGy d ShG? d SmHA d SrHB d SwHH d S{HM d T@HI d TEHI d TJHE d TOHC d TTHE d TXHB d T]G? d TbHC d TgHH d TlHF d TpHC d TuHB d TzG} d T?Gv d UDG{ d UIGy d UMG~ d URHE d UWHJ d U\HQ d UaHX d UfHd d UjHc d UoHd d UtHa d UyHd d U~Ha d VBHb d VGHi d VLHy d VQIV d VVIi d V[I} d V_JK d VdJK d ViJh d VnJz d VsJL d VxI] d V|I@ d WAHn d WFHw d WKIC d WPIf d WTJS d WYJt d W^KB d WcJ{ d WhJv d WmKE d WqKg d WvLL d W{La d X@Lq d XEMJ stroke m XEMJ d XJM^ d XNMb d XSMU d XXMl d X]NH d XbNe d XfOE d XkOJ d XpOS d XuOq d XzPt d X?RL d YCRy d YHRq d YMRt d YRS~ d YWUn d Y\WT d Y`Xg d YeZc d Yj^l d YocN d YtdZ d Yxb] d Y}_P d ZB[d d ZGXg d ZLV\ d ZQTf d ZUSg d ZZS\ d Z_SL d ZdRL d ZiPu d ZmPB d ZrPN d ZwQK d Z|Qe d [APt d [FO@ d [JM\ d [OML d [TMu d [YNK d [^NG d [cM] d [gL\ d [lK] d [qJl d [vJQ d [{JS d [?Ja d \DJK d \II] d \NH| d \SHm d \XHm d \\Hu d \aI] d \fJU d \kKB d \pKR d \uKE d \yJ^ d \~It d ]CIP d ]HI@ d ]MI@ d ]QHs d ]VHv d ][Hn d ]`Hn d ]eHk d ]jHh d ]nH] d ]sHT d ]xHM d ]}HI d ^BHA d ^GHB d ^KHC d ^PG} d ^UGy d ^ZGs d ^_Gq d ^cGu d ^hGv d ^mGy d ^rG{ d ^wG~ d ^|G~ d _@Gy d _EGy d _JG} d _OHA d _TH@ d _XHG d _]HF d _bHF d _gHA d _lGx d _qGv d _uGx d _zGt d _?Gu d `DGx d `IG| d `NGy d `RG| d `WH@ d `\G? d `aG~ d `fHC d `jHL d `oHQ d `tHQ d `yHQ d `~HP d aCHR d aGHY d aLH[ d aQHV d aVHQ d a[HN d a`HN d adHP d aiHN d anHI d asHF d axG? d a|Gv d bAGu d bFG{ d bKHE d bPHL d bUHT d bYHY d b^H^ d bcH^ d bhH[ d bmH[ d brH` d bvHm d b{H~ d c@IV d cEIv d cJJh d cOKU d cSK] d cXKb d c]Kc d cbKO d cgJu d ckJK d cpI_ d cuIC d czHu d c?Hg d dDHV d dHHK d dMHN d dRHS d dWHQ d d\HN d daHN d deHN d djHI d doHI d dtHC d dyG~ d d}H@ d eBGy d eGGt d eLGv d eQGt d eUGl d eZGg d e_Gd d edGd d eiGd d enGe d esGd d ewGl d e|Ge d fAG\ d fFGd d fKGe d fOGf d fTGe d fYGb d f^Gb d fcG^ d fhGW d flG\ d fqG` d fvG` d f{G^ d g@G_ d gDG] d gIG\ d gNGc d gSGj d gXGj d g]Gk d gaGi d gfGd d gkGf d gpGd d guG_ d gzG\ d g~G[ d hCGV d hHGS d hMGT d hRGV d hVGV d h[GT d h`GT d heGV d hjGU d hoGS d hsGS d hxGS d h}GU d iBGV d iGGV d iLGV d iPGT d iUGS d iZGQ d i_GQ d idGQ d ihGO d imGP d irGQ d iwGR d i|GR d jAGT d jEGQ d jJGQ d jOGR d jTGL d jYGL d j^GN d jbGI d jgGG d jlGF d jqGH d jvGE d jzGF d j?GG d kDGC d kIGB d kNGC d kSGB d kWGA d k\F? d kaGA d kfGA d kkF? d koF~ d ktF? d kyF~ d k~F} d lCF| d lHFz d lLF~ d lQF? d lVF| d l[Fz d l`Fz d leF| d liF| d lnF~ d lsFw d lxFy d l}Fy stroke /Times-Roman findfont 24 scalefont setfont initmatrix -1 72 mul 300 div 1 72 mul 300 div scale -2409 88 translate 1600 3150 moveto [1 0 0 -1 0 0] concat (NOAO/IRAF sontag@barabbas.stsci.edu Tue Nov 15 15:43:21 2011 ) show showpage �����������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psdump_prow_256_250.ps�������������������������������������������������0000644�0001750�0001750�00000070733�14203121554�020473� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-2.0 /devppi 300 def /userppi 72 def /pagewidth 8.5 def /devpixtouser { userppi mul devppi div } def /pagetolandscape 90 def /setcoords { pagewidth userppi mul 0 translate pagetolandscape rotate 1 devpixtouser 1 devpixtouser scale } def /setjoins { 1 setlinejoin 1 setlinecap } def erasepage initgraphics setcoords setjoins /getpoint { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } def /m { getpoint moveto } def /d { getpoint lineto } def 02 setlinewidth 05 setlinewidth m F\Fw d GxFw d GxGS d GxFw d IXFw d IXGS d IXFw d JyFw d JyGS d JyFw d LYFw d LYGS d LYFw d MzFw d MzGn d MzFw stroke 02 setlinewidth m MNFA d MNE\ m MOFA d MOE^ m MQFE d MQE\ m MQFE d MLE? d MIE} m MGE\ d MWE\ m MNE^ d MJE\ m MNE` d MLE\ m MQE` d MRE\ m MQE^ d MTE\ m MCEN m MrFE d MnFC d MjE} d MiEs d MiEm d MjEd d MnE^ d MrE\ d MvE\ d MzE^ d M}Ed d M?Em d M?Es d M}E} d MzFC d MvFE d MrFE m MnFA d MlE} d MjEu d MjEk d MlEd d MnE` m MzE` d M|Ed d M}Ek d M}Eu d M|E} d MzFA m MrFE d MoFC d MnE? d MlEu d MlEk d MnEb d MoE^ d MrE\ m MvE\ d MyE^ d MzEb d M|Ek d M|Eu d MzE? d MyFC d MvFE m MgEN m NWFE d NRFC d NOE} d NNEs d NNEm d NOEd d NRE^ d NWE\ d NZE\ d N_E^ d NbEd d NdEm d NdEs d NbE} d N_FC d NZFE d NWFE m NRFA d NQE} d NOEu d NOEk d NQEd d NRE` m N_E` d NaEd d NbEk d NbEu d NaE} d N_FA m NWFE d NTFC d NRE? d NQEu d NQEk d NREb d NTE^ d NWE\ m NZE\ d N]E^ d N_Eb d NaEk d NaEu d N_E? d N]FC d NZFE m NLEN stroke 05 setlinewidth m MzFw d OZFw d OZGS d OZFw d P{Fw d P{GS d P{Fw d R[Fw d R[GS d R[Fw d S{Fw d S{GS d S{Fw d U\Fw d U\Gn d U\Fw stroke 02 setlinewidth m ThE} d ThE{ d TjE{ d TjE} d ThE} m ThE? d TjE? d TkE} d TkE{ d TjEy d ThEy d TfE{ d TfE} d ThFA d TjFC d TnFE d TuFE d TyFC d T{FA d T}E} d T}Ey d T{Eu d TvEq d TnEm d TkEk d ThEg d TfEb d TfE\ m TyFA d T{E} d T{Ey d TyEu m TuFE d TxFC d TyE} d TyEy d TxEu d TuEq d TnEm m TfE` d ThEb d TkEb d TsE` d TyE` d T}Eb m TkEb d TsE^ d TyE^ d T{E` m TkEb d TsE\ d TyE\ d T{E^ d T}Eb d T}Ef m TeEN m UUFE d UPFC d UME} d UKEs d UKEm d UMEd d UPE^ d UUE\ d UXE\ d U]E^ d U`Ed d UaEm d UaEs d U`E} d U]FC d UXFE d UUFE m UPFA d UNE} d UMEu d UMEk d UNEd d UPE` m U]E` d U^Ed d U`Ek d U`Eu d U^E} d U]FA m UUFE d UQFC d UPE? d UNEu d UNEk d UPEb d UQE^ d UUE\ m UXE\ d U[E^ d U]Eb d U^Ek d U^Eu d U]E? d U[FC d UXFE m UIEN m UyFE d UtFC d UqE} d UpEs d UpEm d UqEd d UtE^ d UyE\ d U|E\ d VAE^ d VDEd d VFEm d VFEs d VDE} d VAFC d U|FE d UyFE m UtFA d UsE} d UqEu d UqEk d UsEd d UtE` m VAE` d VCEd d VDEk d VDEu d VCE} d VAFA m UyFE d UvFC d UtE? d UsEu d UsEk d UtEb d UvE^ d UyE\ m U|E\ d V@E^ d VAEb d VCEk d VCEu d VAE? d V@FC d U|FE m UnEN stroke 05 setlinewidth m U\Fw d V|Fw d V|GS d V|Fw d X]Fw d X]GS d X]Fw d Y}Fw d Y}GS d Y}Fw d [^Fw d [^GS d [^Fw d \~Fw d \~Gn d \~Fw stroke 02 setlinewidth m \JE} d \JE{ d \LE{ d \LE} d \JE} m \JE? d \LE? d \ME} d \ME{ d \LEy d \JEy d \IE{ d \IE} d \JFA d \LFC d \QFE d \WFE d \\FC d \]E? d \]Ey d \\Eu d \WEs m \ZFC d \\E? d \\Ey d \ZEu m \UFE d \YFC d \ZE? d \ZEy d \YEu d \UEs m \REs d \WEs d \ZEq d \]Em d \_Ei d \_Ed d \]E` d \\E^ d \WE\ d \QE\ d \LE^ d \JE` d \IEd d \IEf d \JEg d \LEg d \MEf d \MEd d \LEb d \JEb m \\Em d \]Ei d \]Ed d \\E` m \UEs d \YEq d \ZEo d \\Ei d \\Ed d \ZE^ d \WE\ m \JEf d \JEd d \LEd d \LEf d \JEf m \GEN m \wFE d \rFC d \oE} d \mEs d \mEm d \oEd d \rE^ d \wE\ d \zE\ d \?E^ d ]BEd d ]DEm d ]DEs d ]BE} d \?FC d \zFE d \wFE m \rFA d \qE} d \oEu d \oEk d \qEd d \rE` m \?E` d ]@Ed d ]BEk d ]BEu d ]@E} d \?FA m \wFE d \tFC d \rE? d \qEu d \qEk d \rEb d \tE^ d \wE\ m \zE\ d \}E^ d \?Eb d ]@Ek d ]@Eu d \?E? d \}FC d \zFE m \lEN m ]\FE d ]WFC d ]TE} d ]REs d ]REm d ]TEd d ]WE^ d ]\E\ d ]_E\ d ]dE^ d ]gEd d ]hEm d ]hEs d ]gE} d ]dFC d ]_FE d ]\FE m ]WFA d ]UE} d ]TEu d ]TEk d ]UEd d ]WE` m ]dE` d ]eEd d ]gEk d ]gEu d ]eE} d ]dFA m ]\FE d ]XFC d ]WE? d ]UEu d ]UEk d ]WEb d ]XE^ d ]\E\ m ]_E\ d ]bE^ d ]dEb d ]eEk d ]eEu d ]dE? d ]bFC d ]_FE m ]QEN stroke 05 setlinewidth m \~Fw d ^_Fw d ^_GS d ^_Fw d _?Fw d _?GS d _?Fw d a`Fw d a`GS d a`Fw d c@Fw d c@GS d c@Fw d daFw d daGn d daFw stroke 02 setlinewidth m cxE? d cxE\ m cyFA d cyE^ m c{FE d c{E\ m c{FE d ciEg d dCEg m csE\ d d@E\ m cxE^ d ctE\ m cxE` d cvE\ m c{E` d c|E\ m c{E^ d c~E\ m ciEN m dYFE d dTFC d dQE} d dPEs d dPEm d dQEd d dTE^ d dYE\ d d\E\ d daE^ d ddEd d dfEm d dfEs d ddE} d daFC d d\FE d dYFE m dTFA d dSE} d dQEu d dQEk d dSEd d dTE` m daE` d dcEd d ddEk d ddEu d dcE} d daFA m dYFE d dVFC d dTE? d dSEu d dSEk d dTEb d dVE^ d dYE\ m d\E\ d d_E^ d daEb d dcEk d dcEu d daE? d d_FC d d\FE m dNEN m d~FE d dyFC d dvE} d dtEs d dtEm d dvEd d dyE^ d d~E\ d eAE\ d eFE^ d eIEd d eKEm d eKEs d eIE} d eFFC d eAFE d d~FE m dyFA d dxE} d dvEu d dvEk d dxEd d dyE` m eFE` d eGEd d eIEk d eIEu d eGE} d eFFA m d~FE d d{FC d dyE? d dxEu d dxEk d dyEb d d{E^ d d~E\ m eAE\ d eDE^ d eFEb d eGEk d eGEu d eFE? d eDFC d eAFE m dsEN stroke 05 setlinewidth m daFw d fAFw d fAGS d fAFw d gaFw d gaGS d gaFw d iBFw d iBGS d iBFw d jbFw d jbGS d jbFw d lCFw d lCGn d lCFw stroke 02 setlinewidth m kPFE d kMEq d kPEu d kUEw d kZEw d k_Eu d kbEq d kcEk d kcEg d kbEb d k_E^ d kZE\ d kUE\ d kPE^ d kOE` d kMEd d kMEf d kOEg d kPEg d kREf d kREd d kPEb d kOEb m k`Eq d kbEm d kbEf d k`Eb m kZEw d k]Eu d k_Es d k`Em d k`Ef d k_E` d k]E^ d kZE\ m kOEf d kOEd d kPEd d kPEf d kOEf m kPFE d k`FE m kPFC d k]FC m kPFA d kWFA d k]FC d k`FE m kLEN m k{FE d kwFC d ktE} d krEs d krEm d ktEd d kwE^ d k{E\ d k?E\ d lCE^ d lGEd d lHEm d lHEs d lGE} d lCFC d k?FE d k{FE m kwFA d kuE} d ktEu d ktEk d kuEd d kwE` m lCE` d lEEd d lGEk d lGEu d lEE} d lCFA m k{FE d kxFC d kwE? d kuEu d kuEk d kwEb d kxE^ d k{E\ m k?E\ d lBE^ d lCEb d lEEk d lEEu d lCE? d lBFC d k?FE m kpEN m l`FE d l[FC d lXE} d lWEs d lWEm d lXEd d l[E^ d l`E\ d lcE\ d lhE^ d lkEd d lmEm d lmEs d lkE} d lhFC d lcFE d l`FE m l[FA d lZE} d lXEu d lXEk d lZEd d l[E` m lhE` d ljEd d lkEk d lkEu d ljE} d lhFA m l`FE d l]FC d l[E? d lZEu d lZEk d l[Eb d l]E^ d l`E\ m lcE\ d lfE^ d lhEb d ljEk d ljEu d lhE? d lfFC d lcFE m lUEN stroke 05 setlinewidth m lCFw d l}Fw m l}Fw d l}Ga d laGa d l}Ga d l}Hi d laHi d l}Hi d l}Ir d laIr d l}Ir d l}Jz d laJz d l}Jz d l}LC d lELC d l}LC d l}ML d laML d l}ML d l}NT d laNT d l}NT d l}O] d laO] d l}O] d l}Pe d laPe d l}Pe d l}Qn d lEQn d l}Qn d l}Rw d laRw d l}Rw d l}S? d laS? d l}S? d l}UH d laUH d l}UH d l}VP d laVP d l}VP d l}WY d lEWY d l}WY d l}Xa d laXa d l}Xa d l}Yj d laYj d l}Yj d l}Zs d laZs d l}Zs d l}[{ d la[{ d l}[{ d l}]D d lE]D d l}]D d l}^L d la^L d l}^L d l}_U d la_U d l}_U d l}`^ d la`^ d l}`^ d l}af d laaf d l}af d l}bo d lEbo d l}bo d l}cw d lacw d l}cw d l}dZ m F\Fw d F\Ga d FxGa d F\Ga d F\Hi d FxHi d F\Hi d F\Ir d FxIr d F\Ir d F\Jz d FxJz d F\Jz d F\LC d GTLC d F\LC stroke 02 setlinewidth m D_LV d D\LC d D_LG d DdLI d DhLI d DmLG d DpLC d DrK} d DrKy d DpKs d DmKp d DhKn d DdKn d D_Kp d D]Kq d D\Ku d D\Kw d D]Ky d D_Ky d D`Kw d D`Ku d D_Ks d D]Ks m DoLC d DpK? d DpKw d DoKs m DhLI d DlLG d DmLE d DoK? d DoKw d DmKq d DlKp d DhKn m D]Kw d D]Ku d D_Ku d D_Kw d D]Kw m D_LV d DoLV m D_LT d DlLT m D_LR d DeLR d DlLT d DoLV m DZK` m EJLV d EELT d EBLO d E@LE d E@K? d EBKu d EEKp d EJKn d EMKn d ERKp d EUKu d EWK? d EWLE d EULO d ERLT d EMLV d EJLV m EELR d EDLO d EBLG d EBK} d EDKu d EEKq m ERKq d ESKu d EUK} d EULG d ESLO d ERLR m EJLV d EGLT d EELQ d EDLG d EDK} d EEKs d EGKp d EJKn m EMKn d EPKp d ERKs d ESK} d ESLG d ERLQ d EPLT d EMLV m D?K` m EoLV d EjLT d EgLO d EeLE d EeK? d EgKu d EjKp d EoKn d ErKn d EwKp d EzKu d E{K? d E{LE d EzLO d EwLT d ErLV d EoLV m EjLR d EhLO d EgLG d EgK} d EhKu d EjKq m EwKq d ExKu d EzK} d EzLG d ExLO d EwLR m EoLV d EkLT d EjLQ d EhLG d EhK} d EjKs d EkKp d EoKn m ErKn d EuKp d EwKs d ExK} d ExLG d EwLQ d EuLT d ErLV m EdK` stroke 05 setlinewidth m F\LC d F\ML d FxML d F\ML d F\NT d FxNT d F\NT d F\O] d FxO] d F\O] d F\Pe d FxPe d F\Pe d F\Qn d GTQn d F\Qn stroke 02 setlinewidth m DAQ} d DAQY m DBQ} d DBQ[ m DDRA d DDQY m DDRA d C?Q{ d C|Qz m CzQY d DJQY m DAQ[ d C}QY m DAQ\ d C?QY m DDQ\ d DEQY m DDQ[ d DGQY m CuQK m DeRA d D`Q? d D]Qz d D\Qp d D\Qj d D]Q` d D`Q[ d DeQY d DhQY d DmQ[ d DpQ` d DrQj d DrQp d DpQz d DmQ? d DhRA d DeRA m D`Q} d D_Qz d D]Qr d D]Qh d D_Q` d D`Q\ m DmQ\ d DoQ` d DpQh d DpQr d DoQz d DmQ} m DeRA d DbQ? d D`Q{ d D_Qr d D_Qh d D`Q^ d DbQ[ d DeQY m DhQY d DlQ[ d DmQ^ d DoQh d DoQr d DmQ{ d DlQ? d DhRA m DZQK m EJRA d EEQ? d EBQz d E@Qp d E@Qj d EBQ` d EEQ[ d EJQY d EMQY d ERQ[ d EUQ` d EWQj d EWQp d EUQz d ERQ? d EMRA d EJRA m EEQ} d EDQz d EBQr d EBQh d EDQ` d EEQ\ m ERQ\ d ESQ` d EUQh d EUQr d ESQz d ERQ} m EJRA d EGQ? d EEQ{ d EDQr d EDQh d EEQ^ d EGQ[ d EJQY m EMQY d EPQ[ d ERQ^ d ESQh d ESQr d ERQ{ d EPQ? d EMRA m D?QK m EoRA d EjQ? d EgQz d EeQp d EeQj d EgQ` d EjQ[ d EoQY d ErQY d EwQ[ d EzQ` d E{Qj d E{Qp d EzQz d EwQ? d ErRA d EoRA m EjQ} d EhQz d EgQr d EgQh d EhQ` d EjQ\ m EwQ\ d ExQ` d EzQh d EzQr d ExQz d EwQ} m EoRA d EkQ? d EjQ{ d EhQr d EhQh d EjQ^ d EkQ[ d EoQY m ErQY d EuQ[ d EwQ^ d ExQh d ExQr d EwQ{ d EuQ? d ErRA m EdQK stroke 05 setlinewidth m F\Qn d F\Rw d FxRw d F\Rw d F\S? d FxS? d F\S? d F\UH d FxUH d F\UH d F\VP d FxVP d F\VP d F\WY d GTWY d F\WY stroke 02 setlinewidth m DAWh d DAWD m DBWh d DBWE m DDWl d DDWD m DDWl d C?Wf d C|We m CzWD d DJWD m DAWE d C}WD m DAWG d C?WD m DDWG d DEWD m DDWE d DGWD m CuVv m D_Wl d D\WY d D_W] d DdW_ d DhW_ d DmW] d DpWY d DrWS d DrWO d DpWI d DmWE d DhWD d DdWD d D_WE d D]WG d D\WK d D\WM d D]WO d D_WO d D`WM d D`WK d D_WI d D]WI m DoWY d DpWU d DpWM d DoWI m DhW_ d DlW] d DmW[ d DoWU d DoWM d DmWG d DlWE d DhWD m D]WM d D]WK d D_WK d D_WM d D]WM m D_Wl d DoWl m D_Wj d DlWj m D_Wh d DeWh d DlWj d DoWl m DZVv m EJWl d EEWj d EBWe d E@W[ d E@WU d EBWK d EEWE d EJWD d EMWD d ERWE d EUWK d EWWU d EWW[ d EUWe d ERWj d EMWl d EJWl m EEWh d EDWe d EBW] d EBWS d EDWK d EEWG m ERWG d ESWK d EUWS d EUW] d ESWe d ERWh m EJWl d EGWj d EEWf d EDW] d EDWS d EEWI d EGWE d EJWD m EMWD d EPWE d ERWI d ESWS d ESW] d ERWf d EPWj d EMWl m D?Vv m EoWl d EjWj d EgWe d EeW[ d EeWU d EgWK d EjWE d EoWD d ErWD d EwWE d EzWK d E{WU d E{W[ d EzWe d EwWj d ErWl d EoWl m EjWh d EhWe d EgW] d EgWS d EhWK d EjWG m EwWG d ExWK d EzWS d EzW] d ExWe d EwWh m EoWl d EkWj d EjWf d EhW] d EhWS d EjWI d EkWE d EoWD m ErWD d EuWE d EwWI d ExWS d ExW] d EwWf d EuWj d ErWl m EdVv stroke 05 setlinewidth m F\WY d F\Xa d FxXa d F\Xa d F\Yj d FxYj d F\Yj d F\Zs d FxZs d F\Zs d F\[{ d Fx[{ d F\[{ d F\]D d GT]D d F\]D stroke 02 setlinewidth m Cy]P d Cy]N d Cz]N d Cz]P d Cy]P m Cy]Q d Cz]Q d C|]P d C|]N d Cz]L d Cy]L d Cw]N d Cw]P d Cy]S d Cz]U d C?]W d DE]W d DJ]U d DL]S d DM]P d DM]L d DL]H d DG]D d C?]@ d C|\~ d Cy\z d Cw\t d Cw\n m DJ]S d DL]P d DL]L d DJ]H m DE]W d DI]U d DJ]P d DJ]L d DI]H d DE]D d C?]@ m Cw\r d Cy\t d C|\t d DD\r d DJ\r d DM\t m C|\t d DD\p d DJ\p d DL\r m C|\t d DD\n d DJ\n d DL\p d DM\t d DM\x m Cu\a m De]W d D`]U d D]]P d D\]F d D\]@ d D]\v d D`\p d De\n d Dh\n d Dm\p d Dp\v d Dr]@ d Dr]F d Dp]P d Dm]U d Dh]W d De]W m D`]S d D_]P d D]]H d D]\~ d D_\v d D`\r m Dm\r d Do\v d Dp\~ d Dp]H d Do]P d Dm]S m De]W d Db]U d D`]Q d D_]H d D_\~ d D`\t d Db\p d De\n m Dh\n d Dl\p d Dm\t d Do\~ d Do]H d Dm]Q d Dl]U d Dh]W m DZ\a m EJ]W d EE]U d EB]P d E@]F d E@]@ d EB\v d EE\p d EJ\n d EM\n d ER\p d EU\v d EW]@ d EW]F d EU]P d ER]U d EM]W d EJ]W m EE]S d ED]P d EB]H d EB\~ d ED\v d EE\r m ER\r d ES\v d EU\~ d EU]H d ES]P d ER]S m EJ]W d EG]U d EE]Q d ED]H d ED\~ d EE\t d EG\p d EJ\n m EM\n d EP\p d ER\t d ES\~ d ES]H d ER]Q d EP]U d EM]W m D?\a m Eo]W d Ej]U d Eg]P d Ee]F d Ee]@ d Eg\v d Ej\p d Eo\n d Er\n d Ew\p d Ez\v d E{]@ d E{]F d Ez]P d Ew]U d Er]W d Eo]W m Ej]S d Eh]P d Eg]H d Eg\~ d Eh\v d Ej\r m Ew\r d Ex\v d Ez\~ d Ez]H d Ex]P d Ew]S m Eo]W d Ek]U d Ej]Q d Eh]H d Eh\~ d Ej\t d Ek\p d Eo\n m Er\n d Eu\p d Ew\t d Ex\~ d Ex]H d Ew]Q d Eu]U d Er]W m Ed\a stroke 05 setlinewidth m F\]D d F\^L d Fx^L d F\^L d F\_U d Fx_U d F\_U d F\`^ d Fx`^ d F\`^ d F\af d Fxaf d F\af d F\bo d GTbo d F\bo stroke 02 setlinewidth m Cybz d Cybx d Czbx d Czbz d Cybz m Cyb| d Czb| d C|bz d C|bx d Czbw d Cybw d Cwbx d Cwbz d Cyb~ d Czc@ d C?cB d DEcB d DJc@ d DLb~ d DMbz d DMbw d DLbs d DGbo d C?bk d C|bi d Cybe d Cwb_ d CwbY m DJb~ d DLbz d DLbw d DJbs m DEcB d DIc@ d DJbz d DJbw d DIbs d DEbo d C?bk m Cwb] d Cyb_ d C|b_ d DDb] d DJb] d DMb_ m C|b_ d DDb[ d DJb[ d DLb] m C|b_ d DDbY d DJbY d DLb[ d DMb_ d DMbc m CubL m D_cB d D\bo d D_bs d Ddbu d Dhbu d Dmbs d Dpbo d Drbi d Drbe d Dpb_ d Dmb[ d DhbY d DdbY d D_b[ d D]b] d D\ba d D\bc d D]be d D_be d D`bc d D`ba d D_b_ d D]b_ m Dobo d Dpbk d Dpbc d Dob_ m Dhbu d Dlbs d Dmbq d Dobk d Dobc d Dmb] d Dlb[ d DhbY m D]bc d D]ba d D_ba d D_bc d D]bc m D_cB d DocB m D_c@ d Dlc@ m D_b~ d Deb~ d Dlc@ d DocB m DZbL m EJcB d EEc@ d EBbz d E@bq d E@bk d EBba d EEb[ d EJbY d EMbY d ERb[ d EUba d EWbk d EWbq d EUbz d ERc@ d EMcB d EJcB m EEb~ d EDbz d EBbs d EBbi d EDba d EEb] m ERb] d ESba d EUbi d EUbs d ESbz d ERb~ m EJcB d EGc@ d EEb| d EDbs d EDbi d EEb_ d EGb[ d EJbY m EMbY d EPb[ d ERb_ d ESbi d ESbs d ERb| d EPc@ d EMcB m D?bL m EocB d Ejc@ d Egbz d Eebq d Eebk d Egba d Ejb[ d EobY d ErbY d Ewb[ d Ezba d E{bk d E{bq d Ezbz d Ewc@ d ErcB d EocB m Ejb~ d Ehbz d Egbs d Egbi d Ehba d Ejb] m Ewb] d Exba d Ezbi d Ezbs d Exbz d Ewb~ m EocB d Ekc@ d Ejb| d Ehbs d Ehbi d Ejb_ d Ekb[ d EobY m ErbY d Eub[ d Ewb_ d Exbi d Exbs d Ewb| d Euc@ d ErcB m EdbL stroke 05 setlinewidth m F\bo d F\cw d Fxcw d F\cw d F\dZ m F\dZ d GxdZ d Gxc~ d GxdZ d IXdZ d IXc~ d IXdZ d JydZ d Jyc~ d JydZ d LYdZ d LYc~ d LYdZ d MzdZ d Mzcb d MzdZ d OZdZ d OZc~ d OZdZ d P{dZ d P{c~ d P{dZ d R[dZ d R[c~ d R[dZ d S{dZ d S{c~ d S{dZ d U\dZ d U\cb d U\dZ d V|dZ d V|c~ d V|dZ d X]dZ d X]c~ d X]dZ d Y}dZ d Y}c~ d Y}dZ d [^dZ d [^c~ d [^dZ d \~dZ d \~cb d \~dZ d ^_dZ d ^_c~ d ^_dZ d _?dZ d _?c~ d _?dZ d a`dZ d a`c~ d a`dZ d c@dZ d c@c~ d c@dZ d dadZ d dacb d dadZ d fAdZ d fAc~ d fAdZ d gadZ d gac~ d gadZ d iBdZ d iBc~ d iBdZ d jbdZ d jbc~ d jbdZ d lCdZ d lCcb d lCdZ d l}dZ stroke 02 setlinewidth m VEDV d VGD\ d VGDP d VEDV d VBDZ d U?D\ d U{D\ d UvDZ d UsDV d UrDR d UpDL d UpDC d UrC} d UsCy d UvCu d U{Cs d U?Cs d VBCu d VECy d VGC} m UuDV d UsDR d UrDL d UrDC d UsC} d UuCy m U{D\ d UxDZ d UuDT d UsDL d UsDC d UuC{ d UxCu d U{Cs m UoCe m V`DN d V[DL d VXDH d VVDC d VVC? d VXCy d V[Cu d V`Cs d VcCs d VhCu d VkCy d VlC? d VlDC d VkDH d VhDL d VcDN d V`DN m VYDH d VXDD d VXC} d VYCy m ViCy d VkC} d VkDD d ViDH m V`DN d V]DL d V[DJ d VYDD d VYC} d V[Cw d V]Cu d V`Cs m VcCs d VfCu d VhCw d ViC} d ViDD d VhDJ d VfDL d VcDN m VUCe m W@D\ d W@Cs m WCDZ d WCCu m VyD\ d WED\ d WECs m VyCs d WLCs m V|D\ d W@DZ m V~D\ d W@DX m W@Cu d V|Cs m W@Cw d V~Cs m WECw d WGCs m WECu d WJCs m VyCe m WWDN d WWC} d WXCw d WYCu d W\Cs d WaCs d WcCu d WeCw d WfC{ m WXDL d WXC{ d WYCw m WRDN d WYDN d WYC{ d W[Cu d W\Cs m WfDN d WfCs d WmCs m WhDL d WhCu m WbDN d WiDN d WiCs m WTDN d WWDL m WUDN d WWDJ m WiCw d WjCs m WiCu d WlCs m WRCe m W?DN d W?Cs m X@DL d X@Cu m W{DN d XBDN d XBCs m XBDF d XCDJ d XDDL d XGDN d XJDN d XMDL d XNDJ d XODD d XOCs m XMDJ d XNDD d XNCu m XJDN d XLDL d XMDF d XMCs m XODF d XQDJ d XRDL d XTDN d XXDN d XZDL d X\DJ d X]DD d X]Cs m XZDJ d X\DD d X\Cu m XXDN d XYDL d XZDF d XZCs m W{Cs d XECs m XICs d XSCs m XWCs d XaCs m W}DN d W?DL m W~DN d W?DJ m W?Cu d W}Cs m W?Cw d W~Cs m XBCw d XCCs m XBCu d XDCs m XMCu d XJCs m XMCw d XLCs m XOCw d XQCs m XOCu d XRCs m XZCu d XXCs m XZCw d XYCs m X]Cw d X^Cs m X]Cu d X_Cs m W{Ce m XoDN d XoCs m XpDL d XpCu m XjDN d XrDN d XrCs m XrDF d XsDJ d XtDL d XwDN d X{DN d X~DL d Y@DJ d YADD d YACs m X~DJ d Y@DD d Y@Cu m X{DN d X}DL d X~DF d X~Cs m XjCs d XvCs m XzCs d YECs m XlDN d XoDL m XmDN d XoDJ m XoCu d XlCs m XoCw d XmCs m XrCw d XsCs m XrCu d XtCs m X~Cu d X{Cs m X~Cw d X}Cs m YACw d YCCs m YACu d YDCs m XjCe m YTDi m YTCe m ZCDd d Y?D` d Y{DZ d YwDR d YuDH d YuDA d YwCw d Y{Co d Y?Ci d ZCCe m Y{DX d YyDR d YwDJ d YwC? d YyCw d Y{Cq m Y?D` d Y}D\ d Y{DV d YyDJ d YyC? d Y{Cs d Y}Cm d Y?Ci m YrCe m ZSDN d ZSCe m ZTDL d ZTCg m ZNDN d ZVDN d ZVCe m ZVDH d ZWDL d ZZDN d Z^DN d ZbDL d ZeDH d ZgDC d ZgC? d ZeCy d ZbCu d Z^Cs d ZZCs d ZWCu d ZVCy m ZdDH d ZeDD d ZeC} d ZdCy m Z^DN d ZaDL d ZbDJ d ZdDD d ZdC} d ZbCw d ZaCu d Z^Cs m ZNCe d ZZCe m ZPDN d ZSDL m ZQDN d ZSDJ m ZSCg d ZPCe m ZSCi d ZQCe m ZVCi d ZWCe m ZVCg d ZYCe m ZNCe m Z{D\ d Z{DX d [@DX d [@D\ d Z{D\ m Z~D\ d Z~DX m Z{DZ d [@DZ m Z{DN d Z{Cs m Z~DL d Z~Cu m ZtDN d [@DN d [@Cs m ZtCs d [GCs m ZwDN d Z{DL m ZyDN d Z{DJ m Z{Cu d ZwCs m Z{Cw d ZyCs m [@Cw d [BCs m [@Cu d [ECs m ZtCe m [QDN d [`Cs m [RDN d [bCs m [TDN d [dCs m [bDL d [RCu m [MDN d [XDN m []DN d [gDN m [MCs d [WCs m [\Cs d [gCs m [ODN d [RDL m [WDN d [TDL m [_DN d [bDL m [eDN d [bDL m [RCu d [OCs m [RCu d [UCs m [`Cu d []Cs m [bCu d [eCs m [MCe m [wDC d \IDC d \IDF d \HDJ d \FDL d \ADN d [~DN d [yDL d [uDH d [tDC d [tC? d [uCy d [yCu d [~Cs d \ACs d \FCu d \ICy m \HDD d \HDF d \FDJ m [wDH d [uDD d [uC} d [wCy m \FDC d \FDH d \DDL d \ADN m [~DN d [zDL d [yDJ d [wDD d [wC} d [yCw d [zCu d [~Cs m [rCe m \\D\ d \\Cs m \_DZ d \_Cu m \UD\ d \aD\ d \aCs m \UCs d \hCs m \XD\ d \\DZ m \ZD\ d \\DX m \\Cu d \XCs m \\Cw d \ZCs m \aCw d \cCs m \aCu d \fCs m \UCe m ]BDJ d ]DDN d ]DDF d ]BDJ d ]@DL d \}DN d \uDN d \rDL d \pDJ d \pDF d \rDC d \uDA d \~C? d ]BC} d ]DCw m \rDL d \pDF m \rDD d \uDC d \~DA d ]BC? m ]DC} d ]BCu m \pDJ d \rDF d \uDD d \~DC d ]BDA d ]DC} d ]DCw d ]BCu d \~Cs d \wCs d \tCu d \rCw d \pC{ d \pCs d \rCw m \nCe m ]SDd d ]WD` d ][DZ d ]_DR d ]aDH d ]aDA d ]_Cw d ][Co d ]WCi d ]SCe m ][DX d ]]DR d ]_DJ d ]_C? d ]]Cw d ][Cq m ]WD` d ]YD\ d ][DV d ]]DJ d ]]C? d ][Cs d ]YCm d ]WCi m ]OCe m Ukes d UkeJ d UseJ m Umeq d UmeL m Uges d Uoes d UoeJ m Uke_ d Ujec d Ugee d Udee d U_ec d U\e_ d U[eZ d U[eV d U\eP d U_eL d UdeJ d UgeJ d UjeL d UkeP m U^e_ d U\e\ d U\eT d U^eP m Udee d Uaec d U_ea d U^e\ d U^eT d U_eN d UaeL d UdeJ m Uhes d Ukeq m Ujes d Ukeo m UoeN d UpeJ m UoeL d UreJ m UYd| m VDeZ d VVeZ d VVe] d VUea d VSec d VNee d VKee d VFec d VCe_ d VAeZ d VAeV d VCeP d VFeL d VKeJ d VNeJ d VSeL d VVeP m VUe\ d VUe] d VSea m VDe_ d VCe\ d VCeT d VDeP m VSeZ d VSe_ d VRec d VNee m VKee d VHec d VFea d VDe\ d VDeT d VFeN d VHeL d VKeJ m U?d| m Vdee d VoeJ m Vfee d VoeN m Vhee d VpeN m Vyec d VpeN d VoeJ m Vcee d Vmee m Vree d V|ee m Vcee d Vhea m Vkee d Vhec m Vvee d Vyec m V{ee d Vyec m Vcd| m WNe{ d WNeB m WTe{ d WTeB m W[ei d W[ek d WYek d WYeg d W\eg d W\ek d W[eo d WYeq d WTes d WNes d WIeq d WFem d WFeg d WGec d WLe_ d WVe\ d WYeZ d W[eV d W[eP d WYeL m WGeg d WIec d WLea d WVe] d WYe\ d W[eX m WIeq d WGem d WGei d WIee d WLec d WVe_ d W[e\ d W\eX d W\eR d W[eN d WYeL d WTeJ d WNeJ d WIeL d WGeN d WFeR d WFeV d WIeV d WIeR d WGeR d WGeT m WDd| m Wnee d Wnd| m Woec d Wod~ m Wiee d Wqee d Wqd| m Wqe_ d Wrec d Wuee d Wxee d W}ec d X@e_ d XAeZ d XAeV d X@eP d W}eL d WxeJ d WueJ d WreL d WqeP m W~e_ d X@e\ d X@eT d W~eP m Wxee d W{ec d W}ea d W~e\ d W~eT d W}eN d W{eL d WxeJ m Wid| d Wud| m Wkee d Wnec m Wlee d Wnea m Wnd~ d Wkd| m Wne@ d Wld| stroke m Wld| m Wqe@ d Wrd| m Wqd~ d Wtd| m Wid| m XVes d XVeo d X[eo d X[es d XVes m XYes d XYeo m XVeq d X[eq m XVee d XVeJ m XYec d XYeL m XOee d X[ee d X[eJ m XOeJ d XbeJ m XRee d XVec m XTee d XVea m XVeL d XReJ m XVeN d XTeJ m X[eN d X]eJ m X[eL d X_eJ m XOd| m Xkee d X{eJ m Xmee d X}eJ m Xnee d X~eJ m X}ec d XmeL m Xhee d Xsee m Xxee d YAee m XheJ d XreJ m XveJ d YAeJ m Xjee d Xmec m Xree d Xnec m Xzee d X}ec m Y@ee d X}ec m XmeL d XjeJ m XmeL d XpeJ m X{eL d XxeJ m X}eL d Y@eJ m Xhd| m YTee d YRec d YRea d YTe_ d YWe_ d YYea d YYec d YWee d YTee m YTec d YTea d YWea d YWec d YTec m YTeP d YReN d YReL d YTeJ d YWeJ d YYeL d YYeN d YWeP d YTeP m YTeN d YTeL d YWeL d YWeN d YTeN m YMd| m Ydf@ m Ydd| m ZHee d ZHeJ m ZJec d ZJeL m ZCee d ZLee d ZLeJ m ZYea d ZYec d ZWec d ZWe_ d Z[e_ d Z[ec d ZYee d ZUee d ZRec d ZNe_ d ZLeZ m ZCeJ d ZReJ m ZEee d ZHec m ZGee d ZHea m ZHeL d ZEeJ m ZHeN d ZGeJ m ZLeN d ZNeJ m ZLeL d ZPeJ m ZCd| m Zoee d Zjec d Zge_ d ZeeZ d ZeeV d ZgeP d ZjeL d ZoeJ d ZreJ d ZveL d ZzeP d Z{eV d Z{eZ d Zze_ d Zvec d Zree d Zoee m Zhe_ d Zge\ d ZgeT d ZheP m ZxeP d ZzeT d Zze\ d Zxe_ m Zoee d Zkec d Zjea d Zhe\ d ZheT d ZjeN d ZkeL d ZoeJ m ZreJ d ZueL d ZveN d ZxeT d Zxe\ d Zvea d Zuec d Zree m Zcd| m [Kee d [PeJ m [Lee d [PeP m [Nee d [ReP m [Vee d [ReP d [PeJ m [Vee d [[eJ m [Wee d [[eP m [Vee d [Yee d []eP m [aec d []eP d [[eJ m [Hee d [Ree m []ee d [eee m [Hee d [Lec m [Pee d [Nec m [^ee d [aec m [dee d [aec m [Hd| m [sf@ m [sd| m \Uek d \Uei d \Vei d \Vek d \Uek m \Uem d \Vem d \Xek d \Xei d \Veg d \Ueg d \Sei d \Sek d \Ueo d \Veq d \[es d \aes d \feq d \heo d \iek d \ieg d \hec d \ce_ d \[e\ d \XeZ d \UeV d \SeP d \SeJ m \feo d \hek d \heg d \fec m \aes d \eeq d \fek d \feg d \eec d \ae_ d \[e\ m \SeN d \UeP d \XeP d \`eN d \feN d \ieP m \XeP d \`eL d \feL d \heN m \XeP d \`eJ d \feJ d \heL d \ieP d \ieT m \Rd| m \{es d \xe_ d \{ec d ]@ee d ]Eee d ]Iec d ]Me_ d ]NeZ d ]NeV d ]MeP d ]IeL d ]EeJ d ]@eJ d \{eL d \yeN d \xeR d \xeT d \yeV d \{eV d \}eT d \}eR d \{eP d \yeP m ]Ke_ d ]Me\ d ]MeT d ]KeP m ]Eee d ]Hec d ]Iea d ]Ke\ d ]KeT d ]IeN d ]HeL d ]EeJ m \yeT d \yeR d \{eR d \{eT d \yeT m \{es d ]Kes m \{eq d ]Heq m \{eo d ]Aeo d ]Heq d ]Kes m \vd| m ]nem d ]nek d ]pek d ]pem d ]nem m ]peo d ]neo d ]lem d ]lek d ]nei d ]pei d ]qek d ]qem d ]peq d ]les d ]hes d ]ceq d ]`em d ]^ei d ]]ea d ]]eV d ]^eP d ]aeL d ]feJ d ]ieJ d ]neL d ]qeP d ]seV d ]seX d ]qe] d ]nea d ]iec d ]fec d ]cea d ]ae_ d ]`e\ m ]aem d ]`ei d ]^ea d ]^eV d ]`eP d ]aeN m ]peP d ]qeT d ]qeZ d ]pe] m ]hes d ]eeq d ]ceo d ]aek d ]`ec d ]`eV d ]aeP d ]ceL d ]feJ m ]ieJ d ]leL d ]neN d ]peT d ]peZ d ]ne_ d ]lea d ]iec m ][d| m F\F{ d FaF| d FfF| d FkF| d FoF{ d FtF| d FyFy d F~F~ d GCF| d GHFy d GLF| d GQF| d GVFz d G[F~ d G`F| d GeF} d GiF~ d GnGA d GsF? d GxGA d G}GD d HAGA d HFGB d HKGA d HPGD d HUGA d HZGC d H^GD d HcGE d HhGC d HmGE d HrGD d HwGC d H{GE d I@GF d IEGD d IJGF d IOGH d ISGE d IXGD d I]GF d IbGG d IgGD d IlGE d IpGI d IuGM d IzGL d I?GN d JDGN d JIGL d JMGN d JRGQ d JWGN d J\GO d JaGU d JeGT d JjGV d JoGY d JtGY d JyG^ d J~Gc d KBGd d KGGg d KLGn d KQGj d KVGf d KZGb d K_G` d KdG` d KiGd d KnGk d KsGt d KwGq d K|Gn d LAGl d LFGp d LKGn d LPGh d LTGd d LYGf d L^Gf d LcGf d LhGb d LlGc d LqGc d LvGe d L{Gc d M@G` d MEG` d MIG] d MNGY d MSGZ d MXG[ d M]GW d MbG\ d MfG] d MkG_ d MpG\ d MuG` d MzG^ d M~G^ d NCGd d NHGk d NMGi d NRGn d NWGi d N[Gm d N`Gm d NeGl d NjGi d NoGj d NtGi d NxGh d N}Gf d OBGi d OGGn d OLGo d OPGo d OUGn d OZGu d O_Gu d OdGz d OiGy d OmGu d OrGv d OwGq d O|Gt d PAGr d PEGq d PJGw d POG} d PTHD d PYHM d P^HS d PbH_ d PgHg d PlHg d PqHi d PvHn d P{Ht d P?Hu d QDHy d QIH| d QNIC d QSIQ d QWIX d Q\IP d QaIC d QfHt d QkHg d QpH] d QtHV d QyHS d Q~HL d RCHE d RHG? d RMG~ d RQG? d RVG} d R[G~ d R`G| d ReGy d RiGz d RnGy d RsGq d RxGq d R}Gq d SBGt d SFGv d SKGu d SPGx d SUG{ d SZGu d S_Gy d ScGy d ShG? d SmHA d SrHB d SwHH d S{HM d T@HI d TEHI d TJHE d TOHC d TTHE d TXHB d T]G? d TbHC d TgHH d TlHF d TpHC d TuHB d TzG} d T?Gv d UDG{ d UIGy d UMG~ d URHE d UWHJ d U\HQ d UaHX d UfHd d UjHc d UoHd d UtHa d UyHd d U~Ha d VBHb d VGHi d VLHy d VQIV d VVIi d V[I} d V_JK d VdJK d ViJh d VnJz d VsJL d VxI] d V|I@ d WAHn d WFHw d WKIC d WPIf d WTJS d WYJt d W^KB d WcJ{ d WhJv d WmKE d WqKg d WvLL d W{La d X@Lq d XEMJ stroke m XEMJ d XJM^ d XNMb d XSMU d XXMl d X]NH d XbNe d XfOE d XkOJ d XpOS d XuOq d XzPt d X?RL d YCRy d YHRq d YMRt d YRS~ d YWUn d Y\WT d Y`Xg d YeZc d Yj^l d YocN d YtdZ d Yxb] d Y}_P d ZB[d d ZGXg d ZLV\ d ZQTf d ZUSg d ZZS\ d Z_SL d ZdRL d ZiPu d ZmPB d ZrPN d ZwQK d Z|Qe d [APt d [FO@ d [JM\ d [OML d [TMu d [YNK d [^NG d [cM] d [gL\ d [lK] d [qJl d [vJQ d [{JS d [?Ja d \DJK d \II] d \NH| d \SHm d \XHm d \\Hu d \aI] d \fJU d \kKB d \pKR d \uKE d \yJ^ d \~It d ]CIP d ]HI@ d ]MI@ d ]QHs d ]VHv d ][Hn d ]`Hn d ]eHk d ]jHh d ]nH] d ]sHT d ]xHM d ]}HI d ^BHA d ^GHB d ^KHC d ^PG} d ^UGy d ^ZGs d ^_Gq d ^cGu d ^hGv d ^mGy d ^rG{ d ^wG~ d ^|G~ d _@Gy d _EGy d _JG} d _OHA d _TH@ d _XHG d _]HF d _bHF d _gHA d _lGx d _qGv d _uGx d _zGt d _?Gu d `DGx d `IG| d `NGy d `RG| d `WH@ d `\G? d `aG~ d `fHC d `jHL d `oHQ d `tHQ d `yHQ d `~HP d aCHR d aGHY d aLH[ d aQHV d aVHQ d a[HN d a`HN d adHP d aiHN d anHI d asHF d axG? d a|Gv d bAGu d bFG{ d bKHE d bPHL d bUHT d bYHY d b^H^ d bcH^ d bhH[ d bmH[ d brH` d bvHm d b{H~ d c@IV d cEIv d cJJh d cOKU d cSK] d cXKb d c]Kc d cbKO d cgJu d ckJK d cpI_ d cuIC d czHu d c?Hg d dDHV d dHHK d dMHN d dRHS d dWHQ d d\HN d daHN d deHN d djHI d doHI d dtHC d dyG~ d d}H@ d eBGy d eGGt d eLGv d eQGt d eUGl d eZGg d e_Gd d edGd d eiGd d enGe d esGd d ewGl d e|Ge d fAG\ d fFGd d fKGe d fOGf d fTGe d fYGb d f^Gb d fcG^ d fhGW d flG\ d fqG` d fvG` d f{G^ d g@G_ d gDG] d gIG\ d gNGc d gSGj d gXGj d g]Gk d gaGi d gfGd d gkGf d gpGd d guG_ d gzG\ d g~G[ d hCGV d hHGS d hMGT d hRGV d hVGV d h[GT d h`GT d heGV d hjGU d hoGS d hsGS d hxGS d h}GU d iBGV d iGGV d iLGV d iPGT d iUGS d iZGQ d i_GQ d idGQ d ihGO d imGP d irGQ d iwGR d i|GR d jAGT d jEGQ d jJGQ d jOGR d jTGL d jYGL d j^GN d jbGI d jgGG d jlGF d jqGH d jvGE d jzGF d j?GG d kDGC d kIGB d kNGC d kSGB d kWGA d k\F? d kaGA d kfGA d kkF? d koF~ d ktF? d kyF~ d k~F} d lCF| d lHFz d lLF~ d lQF? d lVF| d l[Fz d l`Fz d leF| d liF| d lnF~ d lsFw d lxFy d l}Fy m F\Fy d FaFx d FfF{ d FkF| d FoFy d FtF{ d FyFw d F~Fy d GCF| d GHFy d GLF| d GQFy d GVF} d G[F{ d G`F? d GeF| d GiF} d GnF? d GsF? d GxGA d G}GA d HAF~ d HFGB d HKGD d HPGC d HUGE d HZGF d H^GD d HcGE d HhGG d HmGF d HrGD d HwGF d H{GF d I@F{ d IBFw m IFFw d IJG\ d IOGm d ISGQ d IXGE d I]GE d IbGH d IgGF d IlGF d IpGF d IuGF d IzGK d I?GJ d JDGJ d JIGN d JMGM d JRGN d JWGW d J\G] d JaGa d JeG^ d JjGZ d JoG[ d JtG\ d JyGa d J~Gh d KBGr d KGGw d KLGq d KQGh d KVGe d KZGq d K_HC d KdHM d KiHE d KnG~ d KsGw d KwGy d K|G} d LAG? d LFH@ d LKG~ d LPHA d LTG| d LYGy d L^Gr d LcH@ d LhHF d LlHH d LqGy d LvGi d L{G` d M@G^ d MEGY d MIGU d MNGT d MSGS d MXGX d M]G\ d MbG\ d MfG_ d MkG` d MpG[ d MuG\ d MzG` d M~Gc d NCGf d NHGg d NMGe d NRGf d NWGg d N[Gn d N`Gt d NeGn d NjGn d NoGm d NtGo d NxGp d N}Gn d OBGm d OGGi d OLGi d OPGi d OUGf d OZGf d O_Gh d OdGj d OiGn d OmGq d OrGu d OwGy d O|G? d PAHC d PEHC d PJHC d POHD d PTHF d PYHL d P^HQ d PbH] d PgHi d PlHs d PqIB d PvIN d P{IM d P?IK d QDIH d QIIK d QNI[ d QSJ@ d QWJe d Q\JZ d QaIp d QfIP d QkIK d QpIc d QtIt d QyIk d Q~IX d RCID d RHHq d RMHb d RQHY d RVHP d R[HI d R`HC d ReGz d RiGu d RnGt d RsGp d RxGq d R}Gz d SBG? d SFGz d SKGv d SPGt d SUGt d SZGu d S_G~ d ScHB d ShHD d SmHH d SrHE d SwHC d S{G? d T@HC d TEHK d TJHR d TOHR d TTHK d TXHE d T]HC d TbGy d TgG| d TlGw d TpG{ d TuG| d TzGx d T?G{ d UDGt d UIGt d UMGu d URG} d UWHD d U\HQ d UaHS d UfHY d UjHY d UoHd d UtH} d UyID d U~IL d VBIO d VGIV d VLIL d VQIJ d VVI[ d V[If d V_Ip d VdJK d ViJg d VnJS d VsJI d VxI{ d V|I^ d WAIQ d WFIC d WKHx d WPH{ d WTID d WYIC d W^IF d WcIG d WhIP d WmIo d WqJj d WvKv d W{L| d X@M[ d XEMQ d XJMQ d XNMT stroke m XNMT d XSMQ d XXMV d X]M^ d XbMn d XfMl d XkMn d XpMq d XuNa d XzOk d X?PK d YCPI d YHOt d YMOk d YROe d YWOc d Y\O{ d Y`Pj d YeQZ d YjQu d YoQ| d YtQy d YxQz d Y}Q} d ZBRC d ZGRR d ZLRR d ZQQe d ZUP^ d ZZOc d Z_Nu d ZdMy d ZiM@ d ZmMG d ZrMd d ZwMI d Z|L[ d [ALR d [FLR d [JKm d [OKJ d [TJw d [YJ| d [^Km d [cL^ d [gLq d [lLl d [qLG d [vKI d [{Jd d [?JY d \DIw d \IIN d \NHm d \SHh d \XHg d \\Hl d \aHs d \fIF d \kIU d \pIS d \uIH d \yHv d \~H{ d ]CH} d ]HIA d ]MIP d ]QIK d ]VHx d ][Hm d ]`Hg d ]eHY d ]jHU d ]nHU d ]sHN d ]xHK d ]}G~ d ^BGz d ^GG| d ^KG| d ^PGu d ^UG{ d ^ZG~ d ^_Gy d ^cGu d ^hGv d ^mGt d ^rGq d ^wGr d ^|Gv d _@Gu d _EGn d _JGn d _OGo d _TGs d _XGy d _]G? d _bHC d _gHF d _lHI d _qHA d _uG~ d _zHA d _?G| d `DG| d `IGv d `NGs d `RGs d `WGv d `\Gu d `aGt d `fG{ d `jHC d `oHG d `tHN d `yHN d `~HN d aCHS d aGHT d aLHS d aQHS d aVHT d a[HX d a`HY d adH[ d aiH\ d anH\ d asHY d axHQ d a|HN d bAHN d bFHI d bKHC d bPHI d bUHN d bYHS d b^H[ d bcH^ d bhHa d bmHb d brHa d bvHl d b{IB d c@I\ d cEIe d cJI` d cOIV d cSIO d cXIS d c]Ik d cbIv d cgIk d ckI] d cpIS d cuIF d czHx d c?Hl d dDHc d dHHb d dMHf d dRHl d dWHs d d\Hp d daHf d deHY d djHK d doHM d dtH[ d dyHk d d}Hk d eBH[ d eGHT d eLHR d eQHH d eUHA d eZG| d e_Gx d edGs d eiGq d enGk d esGg d ewGf d e|Gf d fAGd d fFGf d fKGb d fOG_ d fTGi d fYGk d f^Gk d fcGn d fhGq d flGv d fqGq d fvGj d f{Gj d g@Gf d gDG_ d gIGZ d gNG^ d gSGa d gXGd d g]Gd d gaGi d gfGm d gkGt d gpG~ d guG| d gzGt d g~Gs d hCGm d hHGi d hMGf d hRGf d hVGh d h[Gd d h`Gc d heG] d hjGT d hoGN d hsGM d hxGT d h}GZ d iBG^ d iGGa d iLGc d iPG_ d iUGY d iZGY d i_GY d idGT d ihGQ d imGP d irGN d iwGO d i|GN d jAGN d jEGL d jJGK d jOGN d jTGN d jYGL d j^GK d jbGL d jgGJ d jlGI d jqGH d jvGI d jzGI d j?GF d kDGF d kIGD d kNGF d kSGC d kWGA d k\GA d kaGA d kfGA d kkF? d koF| d ktF~ d kyF~ d k~F~ d lCF} d lHF? d lLF~ d lQF~ d lVF~ d l[F~ d l`Fz d leF} d liF{ d lnFy d lsF| d lxFy d l}Fy stroke /Times-Roman findfont 24 scalefont setfont initmatrix -1 72 mul 300 div 1 72 mul 300 div scale -2409 88 translate 1600 3150 moveto [1 0 0 -1 0 0] concat (NOAO/IRAF sontag@barabbas.stsci.edu Tue Nov 15 15:43:22 2011 ) show showpage �������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psdump_prow_256_250_200.ps���������������������������������������������0000644�0001750�0001750�00000077752�14203121554�021064� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-2.0 /devppi 300 def /userppi 72 def /pagewidth 8.5 def /devpixtouser { userppi mul devppi div } def /pagetolandscape 90 def /setcoords { pagewidth userppi mul 0 translate pagetolandscape rotate 1 devpixtouser 1 devpixtouser scale } def /setjoins { 1 setlinejoin 1 setlinecap } def erasepage initgraphics setcoords setjoins /getpoint { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } def /m { getpoint moveto } def /d { getpoint lineto } def 02 setlinewidth 05 setlinewidth m F\Fw d GxFw d GxGS d GxFw d IXFw d IXGS d IXFw d JyFw d JyGS d JyFw d LYFw d LYGS d LYFw d MzFw d MzGn d MzFw stroke 02 setlinewidth m MNFA d MNE\ m MOFA d MOE^ m MQFE d MQE\ m MQFE d MLE? d MIE} m MGE\ d MWE\ m MNE^ d MJE\ m MNE` d MLE\ m MQE` d MRE\ m MQE^ d MTE\ m MCEN m MrFE d MnFC d MjE} d MiEs d MiEm d MjEd d MnE^ d MrE\ d MvE\ d MzE^ d M}Ed d M?Em d M?Es d M}E} d MzFC d MvFE d MrFE m MnFA d MlE} d MjEu d MjEk d MlEd d MnE` m MzE` d M|Ed d M}Ek d M}Eu d M|E} d MzFA m MrFE d MoFC d MnE? d MlEu d MlEk d MnEb d MoE^ d MrE\ m MvE\ d MyE^ d MzEb d M|Ek d M|Eu d MzE? d MyFC d MvFE m MgEN m NWFE d NRFC d NOE} d NNEs d NNEm d NOEd d NRE^ d NWE\ d NZE\ d N_E^ d NbEd d NdEm d NdEs d NbE} d N_FC d NZFE d NWFE m NRFA d NQE} d NOEu d NOEk d NQEd d NRE` m N_E` d NaEd d NbEk d NbEu d NaE} d N_FA m NWFE d NTFC d NRE? d NQEu d NQEk d NREb d NTE^ d NWE\ m NZE\ d N]E^ d N_Eb d NaEk d NaEu d N_E? d N]FC d NZFE m NLEN stroke 05 setlinewidth m MzFw d OZFw d OZGS d OZFw d P{Fw d P{GS d P{Fw d R[Fw d R[GS d R[Fw d S{Fw d S{GS d S{Fw d U\Fw d U\Gn d U\Fw stroke 02 setlinewidth m ThE} d ThE{ d TjE{ d TjE} d ThE} m ThE? d TjE? d TkE} d TkE{ d TjEy d ThEy d TfE{ d TfE} d ThFA d TjFC d TnFE d TuFE d TyFC d T{FA d T}E} d T}Ey d T{Eu d TvEq d TnEm d TkEk d ThEg d TfEb d TfE\ m TyFA d T{E} d T{Ey d TyEu m TuFE d TxFC d TyE} d TyEy d TxEu d TuEq d TnEm m TfE` d ThEb d TkEb d TsE` d TyE` d T}Eb m TkEb d TsE^ d TyE^ d T{E` m TkEb d TsE\ d TyE\ d T{E^ d T}Eb d T}Ef m TeEN m UUFE d UPFC d UME} d UKEs d UKEm d UMEd d UPE^ d UUE\ d UXE\ d U]E^ d U`Ed d UaEm d UaEs d U`E} d U]FC d UXFE d UUFE m UPFA d UNE} d UMEu d UMEk d UNEd d UPE` m U]E` d U^Ed d U`Ek d U`Eu d U^E} d U]FA m UUFE d UQFC d UPE? d UNEu d UNEk d UPEb d UQE^ d UUE\ m UXE\ d U[E^ d U]Eb d U^Ek d U^Eu d U]E? d U[FC d UXFE m UIEN m UyFE d UtFC d UqE} d UpEs d UpEm d UqEd d UtE^ d UyE\ d U|E\ d VAE^ d VDEd d VFEm d VFEs d VDE} d VAFC d U|FE d UyFE m UtFA d UsE} d UqEu d UqEk d UsEd d UtE` m VAE` d VCEd d VDEk d VDEu d VCE} d VAFA m UyFE d UvFC d UtE? d UsEu d UsEk d UtEb d UvE^ d UyE\ m U|E\ d V@E^ d VAEb d VCEk d VCEu d VAE? d V@FC d U|FE m UnEN stroke 05 setlinewidth m U\Fw d V|Fw d V|GS d V|Fw d X]Fw d X]GS d X]Fw d Y}Fw d Y}GS d Y}Fw d [^Fw d [^GS d [^Fw d \~Fw d \~Gn d \~Fw stroke 02 setlinewidth m \JE} d \JE{ d \LE{ d \LE} d \JE} m \JE? d \LE? d \ME} d \ME{ d \LEy d \JEy d \IE{ d \IE} d \JFA d \LFC d \QFE d \WFE d \\FC d \]E? d \]Ey d \\Eu d \WEs m \ZFC d \\E? d \\Ey d \ZEu m \UFE d \YFC d \ZE? d \ZEy d \YEu d \UEs m \REs d \WEs d \ZEq d \]Em d \_Ei d \_Ed d \]E` d \\E^ d \WE\ d \QE\ d \LE^ d \JE` d \IEd d \IEf d \JEg d \LEg d \MEf d \MEd d \LEb d \JEb m \\Em d \]Ei d \]Ed d \\E` m \UEs d \YEq d \ZEo d \\Ei d \\Ed d \ZE^ d \WE\ m \JEf d \JEd d \LEd d \LEf d \JEf m \GEN m \wFE d \rFC d \oE} d \mEs d \mEm d \oEd d \rE^ d \wE\ d \zE\ d \?E^ d ]BEd d ]DEm d ]DEs d ]BE} d \?FC d \zFE d \wFE m \rFA d \qE} d \oEu d \oEk d \qEd d \rE` m \?E` d ]@Ed d ]BEk d ]BEu d ]@E} d \?FA m \wFE d \tFC d \rE? d \qEu d \qEk d \rEb d \tE^ d \wE\ m \zE\ d \}E^ d \?Eb d ]@Ek d ]@Eu d \?E? d \}FC d \zFE m \lEN m ]\FE d ]WFC d ]TE} d ]REs d ]REm d ]TEd d ]WE^ d ]\E\ d ]_E\ d ]dE^ d ]gEd d ]hEm d ]hEs d ]gE} d ]dFC d ]_FE d ]\FE m ]WFA d ]UE} d ]TEu d ]TEk d ]UEd d ]WE` m ]dE` d ]eEd d ]gEk d ]gEu d ]eE} d ]dFA m ]\FE d ]XFC d ]WE? d ]UEu d ]UEk d ]WEb d ]XE^ d ]\E\ m ]_E\ d ]bE^ d ]dEb d ]eEk d ]eEu d ]dE? d ]bFC d ]_FE m ]QEN stroke 05 setlinewidth m \~Fw d ^_Fw d ^_GS d ^_Fw d _?Fw d _?GS d _?Fw d a`Fw d a`GS d a`Fw d c@Fw d c@GS d c@Fw d daFw d daGn d daFw stroke 02 setlinewidth m cxE? d cxE\ m cyFA d cyE^ m c{FE d c{E\ m c{FE d ciEg d dCEg m csE\ d d@E\ m cxE^ d ctE\ m cxE` d cvE\ m c{E` d c|E\ m c{E^ d c~E\ m ciEN m dYFE d dTFC d dQE} d dPEs d dPEm d dQEd d dTE^ d dYE\ d d\E\ d daE^ d ddEd d dfEm d dfEs d ddE} d daFC d d\FE d dYFE m dTFA d dSE} d dQEu d dQEk d dSEd d dTE` m daE` d dcEd d ddEk d ddEu d dcE} d daFA m dYFE d dVFC d dTE? d dSEu d dSEk d dTEb d dVE^ d dYE\ m d\E\ d d_E^ d daEb d dcEk d dcEu d daE? d d_FC d d\FE m dNEN m d~FE d dyFC d dvE} d dtEs d dtEm d dvEd d dyE^ d d~E\ d eAE\ d eFE^ d eIEd d eKEm d eKEs d eIE} d eFFC d eAFE d d~FE m dyFA d dxE} d dvEu d dvEk d dxEd d dyE` m eFE` d eGEd d eIEk d eIEu d eGE} d eFFA m d~FE d d{FC d dyE? d dxEu d dxEk d dyEb d d{E^ d d~E\ m eAE\ d eDE^ d eFEb d eGEk d eGEu d eFE? d eDFC d eAFE m dsEN stroke 05 setlinewidth m daFw d fAFw d fAGS d fAFw d gaFw d gaGS d gaFw d iBFw d iBGS d iBFw d jbFw d jbGS d jbFw d lCFw d lCGn d lCFw stroke 02 setlinewidth m kPFE d kMEq d kPEu d kUEw d kZEw d k_Eu d kbEq d kcEk d kcEg d kbEb d k_E^ d kZE\ d kUE\ d kPE^ d kOE` d kMEd d kMEf d kOEg d kPEg d kREf d kREd d kPEb d kOEb m k`Eq d kbEm d kbEf d k`Eb m kZEw d k]Eu d k_Es d k`Em d k`Ef d k_E` d k]E^ d kZE\ m kOEf d kOEd d kPEd d kPEf d kOEf m kPFE d k`FE m kPFC d k]FC m kPFA d kWFA d k]FC d k`FE m kLEN m k{FE d kwFC d ktE} d krEs d krEm d ktEd d kwE^ d k{E\ d k?E\ d lCE^ d lGEd d lHEm d lHEs d lGE} d lCFC d k?FE d k{FE m kwFA d kuE} d ktEu d ktEk d kuEd d kwE` m lCE` d lEEd d lGEk d lGEu d lEE} d lCFA m k{FE d kxFC d kwE? d kuEu d kuEk d kwEb d kxE^ d k{E\ m k?E\ d lBE^ d lCEb d lEEk d lEEu d lCE? d lBFC d k?FE m kpEN m l`FE d l[FC d lXE} d lWEs d lWEm d lXEd d l[E^ d l`E\ d lcE\ d lhE^ d lkEd d lmEm d lmEs d lkE} d lhFC d lcFE d l`FE m l[FA d lZE} d lXEu d lXEk d lZEd d l[E` m lhE` d ljEd d lkEk d lkEu d ljE} d lhFA m l`FE d l]FC d l[E? d lZEu d lZEk d l[Eb d l]E^ d l`E\ m lcE\ d lfE^ d lhEb d ljEk d ljEu d lhE? d lfFC d lcFE m lUEN stroke 05 setlinewidth m lCFw d l}Fw m l}Fw d l}Ga d laGa d l}Ga d l}Hi d laHi d l}Hi d l}Ir d laIr d l}Ir d l}Jz d laJz d l}Jz d l}LC d lELC d l}LC d l}ML d laML d l}ML d l}NT d laNT d l}NT d l}O] d laO] d l}O] d l}Pe d laPe d l}Pe d l}Qn d lEQn d l}Qn d l}Rw d laRw d l}Rw d l}S? d laS? d l}S? d l}UH d laUH d l}UH d l}VP d laVP d l}VP d l}WY d lEWY d l}WY d l}Xa d laXa d l}Xa d l}Yj d laYj d l}Yj d l}Zs d laZs d l}Zs d l}[{ d la[{ d l}[{ d l}]D d lE]D d l}]D d l}^L d la^L d l}^L d l}_U d la_U d l}_U d l}`^ d la`^ d l}`^ d l}af d laaf d l}af d l}bo d lEbo d l}bo d l}cw d lacw d l}cw d l}dZ m F\Fw d F\Ga d FxGa d F\Ga d F\Hi d FxHi d F\Hi d F\Ir d FxIr d F\Ir d F\Jz d FxJz d F\Jz d F\LC d GTLC d F\LC stroke 02 setlinewidth m D_LV d D\LC d D_LG d DdLI d DhLI d DmLG d DpLC d DrK} d DrKy d DpKs d DmKp d DhKn d DdKn d D_Kp d D]Kq d D\Ku d D\Kw d D]Ky d D_Ky d D`Kw d D`Ku d D_Ks d D]Ks m DoLC d DpK? d DpKw d DoKs m DhLI d DlLG d DmLE d DoK? d DoKw d DmKq d DlKp d DhKn m D]Kw d D]Ku d D_Ku d D_Kw d D]Kw m D_LV d DoLV m D_LT d DlLT m D_LR d DeLR d DlLT d DoLV m DZK` m EJLV d EELT d EBLO d E@LE d E@K? d EBKu d EEKp d EJKn d EMKn d ERKp d EUKu d EWK? d EWLE d EULO d ERLT d EMLV d EJLV m EELR d EDLO d EBLG d EBK} d EDKu d EEKq m ERKq d ESKu d EUK} d EULG d ESLO d ERLR m EJLV d EGLT d EELQ d EDLG d EDK} d EEKs d EGKp d EJKn m EMKn d EPKp d ERKs d ESK} d ESLG d ERLQ d EPLT d EMLV m D?K` m EoLV d EjLT d EgLO d EeLE d EeK? d EgKu d EjKp d EoKn d ErKn d EwKp d EzKu d E{K? d E{LE d EzLO d EwLT d ErLV d EoLV m EjLR d EhLO d EgLG d EgK} d EhKu d EjKq m EwKq d ExKu d EzK} d EzLG d ExLO d EwLR m EoLV d EkLT d EjLQ d EhLG d EhK} d EjKs d EkKp d EoKn m ErKn d EuKp d EwKs d ExK} d ExLG d EwLQ d EuLT d ErLV m EdK` stroke 05 setlinewidth m F\LC d F\ML d FxML d F\ML d F\NT d FxNT d F\NT d F\O] d FxO] d F\O] d F\Pe d FxPe d F\Pe d F\Qn d GTQn d F\Qn stroke 02 setlinewidth m DAQ} d DAQY m DBQ} d DBQ[ m DDRA d DDQY m DDRA d C?Q{ d C|Qz m CzQY d DJQY m DAQ[ d C}QY m DAQ\ d C?QY m DDQ\ d DEQY m DDQ[ d DGQY m CuQK m DeRA d D`Q? d D]Qz d D\Qp d D\Qj d D]Q` d D`Q[ d DeQY d DhQY d DmQ[ d DpQ` d DrQj d DrQp d DpQz d DmQ? d DhRA d DeRA m D`Q} d D_Qz d D]Qr d D]Qh d D_Q` d D`Q\ m DmQ\ d DoQ` d DpQh d DpQr d DoQz d DmQ} m DeRA d DbQ? d D`Q{ d D_Qr d D_Qh d D`Q^ d DbQ[ d DeQY m DhQY d DlQ[ d DmQ^ d DoQh d DoQr d DmQ{ d DlQ? d DhRA m DZQK m EJRA d EEQ? d EBQz d E@Qp d E@Qj d EBQ` d EEQ[ d EJQY d EMQY d ERQ[ d EUQ` d EWQj d EWQp d EUQz d ERQ? d EMRA d EJRA m EEQ} d EDQz d EBQr d EBQh d EDQ` d EEQ\ m ERQ\ d ESQ` d EUQh d EUQr d ESQz d ERQ} m EJRA d EGQ? d EEQ{ d EDQr d EDQh d EEQ^ d EGQ[ d EJQY m EMQY d EPQ[ d ERQ^ d ESQh d ESQr d ERQ{ d EPQ? d EMRA m D?QK m EoRA d EjQ? d EgQz d EeQp d EeQj d EgQ` d EjQ[ d EoQY d ErQY d EwQ[ d EzQ` d E{Qj d E{Qp d EzQz d EwQ? d ErRA d EoRA m EjQ} d EhQz d EgQr d EgQh d EhQ` d EjQ\ m EwQ\ d ExQ` d EzQh d EzQr d ExQz d EwQ} m EoRA d EkQ? d EjQ{ d EhQr d EhQh d EjQ^ d EkQ[ d EoQY m ErQY d EuQ[ d EwQ^ d ExQh d ExQr d EwQ{ d EuQ? d ErRA m EdQK stroke 05 setlinewidth m F\Qn d F\Rw d FxRw d F\Rw d F\S? d FxS? d F\S? d F\UH d FxUH d F\UH d F\VP d FxVP d F\VP d F\WY d GTWY d F\WY stroke 02 setlinewidth m DAWh d DAWD m DBWh d DBWE m DDWl d DDWD m DDWl d C?Wf d C|We m CzWD d DJWD m DAWE d C}WD m DAWG d C?WD m DDWG d DEWD m DDWE d DGWD m CuVv m D_Wl d D\WY d D_W] d DdW_ d DhW_ d DmW] d DpWY d DrWS d DrWO d DpWI d DmWE d DhWD d DdWD d D_WE d D]WG d D\WK d D\WM d D]WO d D_WO d D`WM d D`WK d D_WI d D]WI m DoWY d DpWU d DpWM d DoWI m DhW_ d DlW] d DmW[ d DoWU d DoWM d DmWG d DlWE d DhWD m D]WM d D]WK d D_WK d D_WM d D]WM m D_Wl d DoWl m D_Wj d DlWj m D_Wh d DeWh d DlWj d DoWl m DZVv m EJWl d EEWj d EBWe d E@W[ d E@WU d EBWK d EEWE d EJWD d EMWD d ERWE d EUWK d EWWU d EWW[ d EUWe d ERWj d EMWl d EJWl m EEWh d EDWe d EBW] d EBWS d EDWK d EEWG m ERWG d ESWK d EUWS d EUW] d ESWe d ERWh m EJWl d EGWj d EEWf d EDW] d EDWS d EEWI d EGWE d EJWD m EMWD d EPWE d ERWI d ESWS d ESW] d ERWf d EPWj d EMWl m D?Vv m EoWl d EjWj d EgWe d EeW[ d EeWU d EgWK d EjWE d EoWD d ErWD d EwWE d EzWK d E{WU d E{W[ d EzWe d EwWj d ErWl d EoWl m EjWh d EhWe d EgW] d EgWS d EhWK d EjWG m EwWG d ExWK d EzWS d EzW] d ExWe d EwWh m EoWl d EkWj d EjWf d EhW] d EhWS d EjWI d EkWE d EoWD m ErWD d EuWE d EwWI d ExWS d ExW] d EwWf d EuWj d ErWl m EdVv stroke 05 setlinewidth m F\WY d F\Xa d FxXa d F\Xa d F\Yj d FxYj d F\Yj d F\Zs d FxZs d F\Zs d F\[{ d Fx[{ d F\[{ d F\]D d GT]D d F\]D stroke 02 setlinewidth m Cy]P d Cy]N d Cz]N d Cz]P d Cy]P m Cy]Q d Cz]Q d C|]P d C|]N d Cz]L d Cy]L d Cw]N d Cw]P d Cy]S d Cz]U d C?]W d DE]W d DJ]U d DL]S d DM]P d DM]L d DL]H d DG]D d C?]@ d C|\~ d Cy\z d Cw\t d Cw\n m DJ]S d DL]P d DL]L d DJ]H m DE]W d DI]U d DJ]P d DJ]L d DI]H d DE]D d C?]@ m Cw\r d Cy\t d C|\t d DD\r d DJ\r d DM\t m C|\t d DD\p d DJ\p d DL\r m C|\t d DD\n d DJ\n d DL\p d DM\t d DM\x m Cu\a m De]W d D`]U d D]]P d D\]F d D\]@ d D]\v d D`\p d De\n d Dh\n d Dm\p d Dp\v d Dr]@ d Dr]F d Dp]P d Dm]U d Dh]W d De]W m D`]S d D_]P d D]]H d D]\~ d D_\v d D`\r m Dm\r d Do\v d Dp\~ d Dp]H d Do]P d Dm]S m De]W d Db]U d D`]Q d D_]H d D_\~ d D`\t d Db\p d De\n m Dh\n d Dl\p d Dm\t d Do\~ d Do]H d Dm]Q d Dl]U d Dh]W m DZ\a m EJ]W d EE]U d EB]P d E@]F d E@]@ d EB\v d EE\p d EJ\n d EM\n d ER\p d EU\v d EW]@ d EW]F d EU]P d ER]U d EM]W d EJ]W m EE]S d ED]P d EB]H d EB\~ d ED\v d EE\r m ER\r d ES\v d EU\~ d EU]H d ES]P d ER]S m EJ]W d EG]U d EE]Q d ED]H d ED\~ d EE\t d EG\p d EJ\n m EM\n d EP\p d ER\t d ES\~ d ES]H d ER]Q d EP]U d EM]W m D?\a m Eo]W d Ej]U d Eg]P d Ee]F d Ee]@ d Eg\v d Ej\p d Eo\n d Er\n d Ew\p d Ez\v d E{]@ d E{]F d Ez]P d Ew]U d Er]W d Eo]W m Ej]S d Eh]P d Eg]H d Eg\~ d Eh\v d Ej\r m Ew\r d Ex\v d Ez\~ d Ez]H d Ex]P d Ew]S m Eo]W d Ek]U d Ej]Q d Eh]H d Eh\~ d Ej\t d Ek\p d Eo\n m Er\n d Eu\p d Ew\t d Ex\~ d Ex]H d Ew]Q d Eu]U d Er]W m Ed\a stroke 05 setlinewidth m F\]D d F\^L d Fx^L d F\^L d F\_U d Fx_U d F\_U d F\`^ d Fx`^ d F\`^ d F\af d Fxaf d F\af d F\bo d GTbo d F\bo stroke 02 setlinewidth m Cybz d Cybx d Czbx d Czbz d Cybz m Cyb| d Czb| d C|bz d C|bx d Czbw d Cybw d Cwbx d Cwbz d Cyb~ d Czc@ d C?cB d DEcB d DJc@ d DLb~ d DMbz d DMbw d DLbs d DGbo d C?bk d C|bi d Cybe d Cwb_ d CwbY m DJb~ d DLbz d DLbw d DJbs m DEcB d DIc@ d DJbz d DJbw d DIbs d DEbo d C?bk m Cwb] d Cyb_ d C|b_ d DDb] d DJb] d DMb_ m C|b_ d DDb[ d DJb[ d DLb] m C|b_ d DDbY d DJbY d DLb[ d DMb_ d DMbc m CubL m D_cB d D\bo d D_bs d Ddbu d Dhbu d Dmbs d Dpbo d Drbi d Drbe d Dpb_ d Dmb[ d DhbY d DdbY d D_b[ d D]b] d D\ba d D\bc d D]be d D_be d D`bc d D`ba d D_b_ d D]b_ m Dobo d Dpbk d Dpbc d Dob_ m Dhbu d Dlbs d Dmbq d Dobk d Dobc d Dmb] d Dlb[ d DhbY m D]bc d D]ba d D_ba d D_bc d D]bc m D_cB d DocB m D_c@ d Dlc@ m D_b~ d Deb~ d Dlc@ d DocB m DZbL m EJcB d EEc@ d EBbz d E@bq d E@bk d EBba d EEb[ d EJbY d EMbY d ERb[ d EUba d EWbk d EWbq d EUbz d ERc@ d EMcB d EJcB m EEb~ d EDbz d EBbs d EBbi d EDba d EEb] m ERb] d ESba d EUbi d EUbs d ESbz d ERb~ m EJcB d EGc@ d EEb| d EDbs d EDbi d EEb_ d EGb[ d EJbY m EMbY d EPb[ d ERb_ d ESbi d ESbs d ERb| d EPc@ d EMcB m D?bL m EocB d Ejc@ d Egbz d Eebq d Eebk d Egba d Ejb[ d EobY d ErbY d Ewb[ d Ezba d E{bk d E{bq d Ezbz d Ewc@ d ErcB d EocB m Ejb~ d Ehbz d Egbs d Egbi d Ehba d Ejb] m Ewb] d Exba d Ezbi d Ezbs d Exbz d Ewb~ m EocB d Ekc@ d Ejb| d Ehbs d Ehbi d Ejb_ d Ekb[ d EobY m ErbY d Eub[ d Ewb_ d Exbi d Exbs d Ewb| d Euc@ d ErcB m EdbL stroke 05 setlinewidth m F\bo d F\cw d Fxcw d F\cw d F\dZ m F\dZ d GxdZ d Gxc~ d GxdZ d IXdZ d IXc~ d IXdZ d JydZ d Jyc~ d JydZ d LYdZ d LYc~ d LYdZ d MzdZ d Mzcb d MzdZ d OZdZ d OZc~ d OZdZ d P{dZ d P{c~ d P{dZ d R[dZ d R[c~ d R[dZ d S{dZ d S{c~ d S{dZ d U\dZ d U\cb d U\dZ d V|dZ d V|c~ d V|dZ d X]dZ d X]c~ d X]dZ d Y}dZ d Y}c~ d Y}dZ d [^dZ d [^c~ d [^dZ d \~dZ d \~cb d \~dZ d ^_dZ d ^_c~ d ^_dZ d _?dZ d _?c~ d _?dZ d a`dZ d a`c~ d a`dZ d c@dZ d c@c~ d c@dZ d dadZ d dacb d dadZ d fAdZ d fAc~ d fAdZ d gadZ d gac~ d gadZ d iBdZ d iBc~ d iBdZ d jbdZ d jbc~ d jbdZ d lCdZ d lCcb d lCdZ d l}dZ stroke 02 setlinewidth m VEDV d VGD\ d VGDP d VEDV d VBDZ d U?D\ d U{D\ d UvDZ d UsDV d UrDR d UpDL d UpDC d UrC} d UsCy d UvCu d U{Cs d U?Cs d VBCu d VECy d VGC} m UuDV d UsDR d UrDL d UrDC d UsC} d UuCy m U{D\ d UxDZ d UuDT d UsDL d UsDC d UuC{ d UxCu d U{Cs m UoCe m V`DN d V[DL d VXDH d VVDC d VVC? d VXCy d V[Cu d V`Cs d VcCs d VhCu d VkCy d VlC? d VlDC d VkDH d VhDL d VcDN d V`DN m VYDH d VXDD d VXC} d VYCy m ViCy d VkC} d VkDD d ViDH m V`DN d V]DL d V[DJ d VYDD d VYC} d V[Cw d V]Cu d V`Cs m VcCs d VfCu d VhCw d ViC} d ViDD d VhDJ d VfDL d VcDN m VUCe m W@D\ d W@Cs m WCDZ d WCCu m VyD\ d WED\ d WECs m VyCs d WLCs m V|D\ d W@DZ m V~D\ d W@DX m W@Cu d V|Cs m W@Cw d V~Cs m WECw d WGCs m WECu d WJCs m VyCe m WWDN d WWC} d WXCw d WYCu d W\Cs d WaCs d WcCu d WeCw d WfC{ m WXDL d WXC{ d WYCw m WRDN d WYDN d WYC{ d W[Cu d W\Cs m WfDN d WfCs d WmCs m WhDL d WhCu m WbDN d WiDN d WiCs m WTDN d WWDL m WUDN d WWDJ m WiCw d WjCs m WiCu d WlCs m WRCe m W?DN d W?Cs m X@DL d X@Cu m W{DN d XBDN d XBCs m XBDF d XCDJ d XDDL d XGDN d XJDN d XMDL d XNDJ d XODD d XOCs m XMDJ d XNDD d XNCu m XJDN d XLDL d XMDF d XMCs m XODF d XQDJ d XRDL d XTDN d XXDN d XZDL d X\DJ d X]DD d X]Cs m XZDJ d X\DD d X\Cu m XXDN d XYDL d XZDF d XZCs m W{Cs d XECs m XICs d XSCs m XWCs d XaCs m W}DN d W?DL m W~DN d W?DJ m W?Cu d W}Cs m W?Cw d W~Cs m XBCw d XCCs m XBCu d XDCs m XMCu d XJCs m XMCw d XLCs m XOCw d XQCs m XOCu d XRCs m XZCu d XXCs m XZCw d XYCs m X]Cw d X^Cs m X]Cu d X_Cs m W{Ce m XoDN d XoCs m XpDL d XpCu m XjDN d XrDN d XrCs m XrDF d XsDJ d XtDL d XwDN d X{DN d X~DL d Y@DJ d YADD d YACs m X~DJ d Y@DD d Y@Cu m X{DN d X}DL d X~DF d X~Cs m XjCs d XvCs m XzCs d YECs m XlDN d XoDL m XmDN d XoDJ m XoCu d XlCs m XoCw d XmCs m XrCw d XsCs m XrCu d XtCs m X~Cu d X{Cs m X~Cw d X}Cs m YACw d YCCs m YACu d YDCs m XjCe m YTDi m YTCe m ZCDd d Y?D` d Y{DZ d YwDR d YuDH d YuDA d YwCw d Y{Co d Y?Ci d ZCCe m Y{DX d YyDR d YwDJ d YwC? d YyCw d Y{Cq m Y?D` d Y}D\ d Y{DV d YyDJ d YyC? d Y{Cs d Y}Cm d Y?Ci m YrCe m ZSDN d ZSCe m ZTDL d ZTCg m ZNDN d ZVDN d ZVCe m ZVDH d ZWDL d ZZDN d Z^DN d ZbDL d ZeDH d ZgDC d ZgC? d ZeCy d ZbCu d Z^Cs d ZZCs d ZWCu d ZVCy m ZdDH d ZeDD d ZeC} d ZdCy m Z^DN d ZaDL d ZbDJ d ZdDD d ZdC} d ZbCw d ZaCu d Z^Cs m ZNCe d ZZCe m ZPDN d ZSDL m ZQDN d ZSDJ m ZSCg d ZPCe m ZSCi d ZQCe m ZVCi d ZWCe m ZVCg d ZYCe m ZNCe m Z{D\ d Z{DX d [@DX d [@D\ d Z{D\ m Z~D\ d Z~DX m Z{DZ d [@DZ m Z{DN d Z{Cs m Z~DL d Z~Cu m ZtDN d [@DN d [@Cs m ZtCs d [GCs m ZwDN d Z{DL m ZyDN d Z{DJ m Z{Cu d ZwCs m Z{Cw d ZyCs m [@Cw d [BCs m [@Cu d [ECs m ZtCe m [QDN d [`Cs m [RDN d [bCs m [TDN d [dCs m [bDL d [RCu m [MDN d [XDN m []DN d [gDN m [MCs d [WCs m [\Cs d [gCs m [ODN d [RDL m [WDN d [TDL m [_DN d [bDL m [eDN d [bDL m [RCu d [OCs m [RCu d [UCs m [`Cu d []Cs m [bCu d [eCs m [MCe m [wDC d \IDC d \IDF d \HDJ d \FDL d \ADN d [~DN d [yDL d [uDH d [tDC d [tC? d [uCy d [yCu d [~Cs d \ACs d \FCu d \ICy m \HDD d \HDF d \FDJ m [wDH d [uDD d [uC} d [wCy m \FDC d \FDH d \DDL d \ADN m [~DN d [zDL d [yDJ d [wDD d [wC} d [yCw d [zCu d [~Cs m [rCe m \\D\ d \\Cs m \_DZ d \_Cu m \UD\ d \aD\ d \aCs m \UCs d \hCs m \XD\ d \\DZ m \ZD\ d \\DX m \\Cu d \XCs m \\Cw d \ZCs m \aCw d \cCs m \aCu d \fCs m \UCe m ]BDJ d ]DDN d ]DDF d ]BDJ d ]@DL d \}DN d \uDN d \rDL d \pDJ d \pDF d \rDC d \uDA d \~C? d ]BC} d ]DCw m \rDL d \pDF m \rDD d \uDC d \~DA d ]BC? m ]DC} d ]BCu m \pDJ d \rDF d \uDD d \~DC d ]BDA d ]DC} d ]DCw d ]BCu d \~Cs d \wCs d \tCu d \rCw d \pC{ d \pCs d \rCw m \nCe m ]SDd d ]WD` d ][DZ d ]_DR d ]aDH d ]aDA d ]_Cw d ][Co d ]WCi d ]SCe m ][DX d ]]DR d ]_DJ d ]_C? d ]]Cw d ][Cq m ]WD` d ]YD\ d ][DV d ]]DJ d ]]C? d ][Cs d ]YCm d ]WCi m ]OCe m Ukes d UkeJ d UseJ m Umeq d UmeL m Uges d Uoes d UoeJ m Uke_ d Ujec d Ugee d Udee d U_ec d U\e_ d U[eZ d U[eV d U\eP d U_eL d UdeJ d UgeJ d UjeL d UkeP m U^e_ d U\e\ d U\eT d U^eP m Udee d Uaec d U_ea d U^e\ d U^eT d U_eN d UaeL d UdeJ m Uhes d Ukeq m Ujes d Ukeo m UoeN d UpeJ m UoeL d UreJ m UYd| m VDeZ d VVeZ d VVe] d VUea d VSec d VNee d VKee d VFec d VCe_ d VAeZ d VAeV d VCeP d VFeL d VKeJ d VNeJ d VSeL d VVeP m VUe\ d VUe] d VSea m VDe_ d VCe\ d VCeT d VDeP m VSeZ d VSe_ d VRec d VNee m VKee d VHec d VFea d VDe\ d VDeT d VFeN d VHeL d VKeJ m U?d| m Vdee d VoeJ m Vfee d VoeN m Vhee d VpeN m Vyec d VpeN d VoeJ m Vcee d Vmee m Vree d V|ee m Vcee d Vhea m Vkee d Vhec m Vvee d Vyec m V{ee d Vyec m Vcd| m WNe{ d WNeB m WTe{ d WTeB m W[ei d W[ek d WYek d WYeg d W\eg d W\ek d W[eo d WYeq d WTes d WNes d WIeq d WFem d WFeg d WGec d WLe_ d WVe\ d WYeZ d W[eV d W[eP d WYeL m WGeg d WIec d WLea d WVe] d WYe\ d W[eX m WIeq d WGem d WGei d WIee d WLec d WVe_ d W[e\ d W\eX d W\eR d W[eN d WYeL d WTeJ d WNeJ d WIeL d WGeN d WFeR d WFeV d WIeV d WIeR d WGeR d WGeT m WDd| m Wnee d Wnd| m Woec d Wod~ m Wiee d Wqee d Wqd| m Wqe_ d Wrec d Wuee d Wxee d W}ec d X@e_ d XAeZ d XAeV d X@eP d W}eL d WxeJ d WueJ d WreL d WqeP m W~e_ d X@e\ d X@eT d W~eP m Wxee d W{ec d W}ea d W~e\ d W~eT d W}eN d W{eL d WxeJ m Wid| d Wud| m Wkee d Wnec m Wlee d Wnea m Wnd~ d Wkd| m Wne@ d Wld| stroke m Wld| m Wqe@ d Wrd| m Wqd~ d Wtd| m Wid| m XVes d XVeo d X[eo d X[es d XVes m XYes d XYeo m XVeq d X[eq m XVee d XVeJ m XYec d XYeL m XOee d X[ee d X[eJ m XOeJ d XbeJ m XRee d XVec m XTee d XVea m XVeL d XReJ m XVeN d XTeJ m X[eN d X]eJ m X[eL d X_eJ m XOd| m Xkee d X{eJ m Xmee d X}eJ m Xnee d X~eJ m X}ec d XmeL m Xhee d Xsee m Xxee d YAee m XheJ d XreJ m XveJ d YAeJ m Xjee d Xmec m Xree d Xnec m Xzee d X}ec m Y@ee d X}ec m XmeL d XjeJ m XmeL d XpeJ m X{eL d XxeJ m X}eL d Y@eJ m Xhd| m YTee d YRec d YRea d YTe_ d YWe_ d YYea d YYec d YWee d YTee m YTec d YTea d YWea d YWec d YTec m YTeP d YReN d YReL d YTeJ d YWeJ d YYeL d YYeN d YWeP d YTeP m YTeN d YTeL d YWeL d YWeN d YTeN m YMd| m Ydf@ m Ydd| m ZHee d ZHeJ m ZJec d ZJeL m ZCee d ZLee d ZLeJ m ZYea d ZYec d ZWec d ZWe_ d Z[e_ d Z[ec d ZYee d ZUee d ZRec d ZNe_ d ZLeZ m ZCeJ d ZReJ m ZEee d ZHec m ZGee d ZHea m ZHeL d ZEeJ m ZHeN d ZGeJ m ZLeN d ZNeJ m ZLeL d ZPeJ m ZCd| m Zoee d Zjec d Zge_ d ZeeZ d ZeeV d ZgeP d ZjeL d ZoeJ d ZreJ d ZveL d ZzeP d Z{eV d Z{eZ d Zze_ d Zvec d Zree d Zoee m Zhe_ d Zge\ d ZgeT d ZheP m ZxeP d ZzeT d Zze\ d Zxe_ m Zoee d Zkec d Zjea d Zhe\ d ZheT d ZjeN d ZkeL d ZoeJ m ZreJ d ZueL d ZveN d ZxeT d Zxe\ d Zvea d Zuec d Zree m Zcd| m [Kee d [PeJ m [Lee d [PeP m [Nee d [ReP m [Vee d [ReP d [PeJ m [Vee d [[eJ m [Wee d [[eP m [Vee d [Yee d []eP m [aec d []eP d [[eJ m [Hee d [Ree m []ee d [eee m [Hee d [Lec m [Pee d [Nec m [^ee d [aec m [dee d [aec m [Hd| m [sf@ m [sd| m \Uek d \Uei d \Vei d \Vek d \Uek m \Uem d \Vem d \Xek d \Xei d \Veg d \Ueg d \Sei d \Sek d \Ueo d \Veq d \[es d \aes d \feq d \heo d \iek d \ieg d \hec d \ce_ d \[e\ d \XeZ d \UeV d \SeP d \SeJ m \feo d \hek d \heg d \fec m \aes d \eeq d \fek d \feg d \eec d \ae_ d \[e\ m \SeN d \UeP d \XeP d \`eN d \feN d \ieP m \XeP d \`eL d \feL d \heN m \XeP d \`eJ d \feJ d \heL d \ieP d \ieT m \Rd| m \{es d \xe_ d \{ec d ]@ee d ]Eee d ]Iec d ]Me_ d ]NeZ d ]NeV d ]MeP d ]IeL d ]EeJ d ]@eJ d \{eL d \yeN d \xeR d \xeT d \yeV d \{eV d \}eT d \}eR d \{eP d \yeP m ]Ke_ d ]Me\ d ]MeT d ]KeP m ]Eee d ]Hec d ]Iea d ]Ke\ d ]KeT d ]IeN d ]HeL d ]EeJ m \yeT d \yeR d \{eR d \{eT d \yeT m \{es d ]Kes m \{eq d ]Heq m \{eo d ]Aeo d ]Heq d ]Kes m \vd| m ]nem d ]nek d ]pek d ]pem d ]nem m ]peo d ]neo d ]lem d ]lek d ]nei d ]pei d ]qek d ]qem d ]peq d ]les d ]hes d ]ceq d ]`em d ]^ei d ]]ea d ]]eV d ]^eP d ]aeL d ]feJ d ]ieJ d ]neL d ]qeP d ]seV d ]seX d ]qe] d ]nea d ]iec d ]fec d ]cea d ]ae_ d ]`e\ m ]aem d ]`ei d ]^ea d ]^eV d ]`eP d ]aeN m ]peP d ]qeT d ]qeZ d ]pe] m ]hes d ]eeq d ]ceo d ]aek d ]`ec d ]`eV d ]aeP d ]ceL d ]feJ m ]ieJ d ]leL d ]neN d ]peT d ]peZ d ]ne_ d ]lea d ]iec m ][d| m F\F{ d FaF| d FfF| d FkF| d FoF{ d FtF| d FyFy d F~F~ d GCF| d GHFy d GLF| d GQF| d GVFz d G[F~ d G`F| d GeF} d GiF~ d GnGA d GsF? d GxGA d G}GD d HAGA d HFGB d HKGA d HPGD d HUGA d HZGC d H^GD d HcGE d HhGC d HmGE d HrGD d HwGC d H{GE d I@GF d IEGD d IJGF d IOGH d ISGE d IXGD d I]GF d IbGG d IgGD d IlGE d IpGI d IuGM d IzGL d I?GN d JDGN d JIGL d JMGN d JRGQ d JWGN d J\GO d JaGU d JeGT d JjGV d JoGY d JtGY d JyG^ d J~Gc d KBGd d KGGg d KLGn d KQGj d KVGf d KZGb d K_G` d KdG` d KiGd d KnGk d KsGt d KwGq d K|Gn d LAGl d LFGp d LKGn d LPGh d LTGd d LYGf d L^Gf d LcGf d LhGb d LlGc d LqGc d LvGe d L{Gc d M@G` d MEG` d MIG] d MNGY d MSGZ d MXG[ d M]GW d MbG\ d MfG] d MkG_ d MpG\ d MuG` d MzG^ d M~G^ d NCGd d NHGk d NMGi d NRGn d NWGi d N[Gm d N`Gm d NeGl d NjGi d NoGj d NtGi d NxGh d N}Gf d OBGi d OGGn d OLGo d OPGo d OUGn d OZGu d O_Gu d OdGz d OiGy d OmGu d OrGv d OwGq d O|Gt d PAGr d PEGq d PJGw d POG} d PTHD d PYHM d P^HS d PbH_ d PgHg d PlHg d PqHi d PvHn d P{Ht d P?Hu d QDHy d QIH| d QNIC d QSIQ d QWIX d Q\IP d QaIC d QfHt d QkHg d QpH] d QtHV d QyHS d Q~HL d RCHE d RHG? d RMG~ d RQG? d RVG} d R[G~ d R`G| d ReGy d RiGz d RnGy d RsGq d RxGq d R}Gq d SBGt d SFGv d SKGu d SPGx d SUG{ d SZGu d S_Gy d ScGy d ShG? d SmHA d SrHB d SwHH d S{HM d T@HI d TEHI d TJHE d TOHC d TTHE d TXHB d T]G? d TbHC d TgHH d TlHF d TpHC d TuHB d TzG} d T?Gv d UDG{ d UIGy d UMG~ d URHE d UWHJ d U\HQ d UaHX d UfHd d UjHc d UoHd d UtHa d UyHd d U~Ha d VBHb d VGHi d VLHy d VQIV d VVIi d V[I} d V_JK d VdJK d ViJh d VnJz d VsJL d VxI] d V|I@ d WAHn d WFHw d WKIC d WPIf d WTJS d WYJt d W^KB d WcJ{ d WhJv d WmKE d WqKg d WvLL d W{La d X@Lq d XEMJ stroke m XEMJ d XJM^ d XNMb d XSMU d XXMl d X]NH d XbNe d XfOE d XkOJ d XpOS d XuOq d XzPt d X?RL d YCRy d YHRq d YMRt d YRS~ d YWUn d Y\WT d Y`Xg d YeZc d Yj^l d YocN d YtdZ d Yxb] d Y}_P d ZB[d d ZGXg d ZLV\ d ZQTf d ZUSg d ZZS\ d Z_SL d ZdRL d ZiPu d ZmPB d ZrPN d ZwQK d Z|Qe d [APt d [FO@ d [JM\ d [OML d [TMu d [YNK d [^NG d [cM] d [gL\ d [lK] d [qJl d [vJQ d [{JS d [?Ja d \DJK d \II] d \NH| d \SHm d \XHm d \\Hu d \aI] d \fJU d \kKB d \pKR d \uKE d \yJ^ d \~It d ]CIP d ]HI@ d ]MI@ d ]QHs d ]VHv d ][Hn d ]`Hn d ]eHk d ]jHh d ]nH] d ]sHT d ]xHM d ]}HI d ^BHA d ^GHB d ^KHC d ^PG} d ^UGy d ^ZGs d ^_Gq d ^cGu d ^hGv d ^mGy d ^rG{ d ^wG~ d ^|G~ d _@Gy d _EGy d _JG} d _OHA d _TH@ d _XHG d _]HF d _bHF d _gHA d _lGx d _qGv d _uGx d _zGt d _?Gu d `DGx d `IG| d `NGy d `RG| d `WH@ d `\G? d `aG~ d `fHC d `jHL d `oHQ d `tHQ d `yHQ d `~HP d aCHR d aGHY d aLH[ d aQHV d aVHQ d a[HN d a`HN d adHP d aiHN d anHI d asHF d axG? d a|Gv d bAGu d bFG{ d bKHE d bPHL d bUHT d bYHY d b^H^ d bcH^ d bhH[ d bmH[ d brH` d bvHm d b{H~ d c@IV d cEIv d cJJh d cOKU d cSK] d cXKb d c]Kc d cbKO d cgJu d ckJK d cpI_ d cuIC d czHu d c?Hg d dDHV d dHHK d dMHN d dRHS d dWHQ d d\HN d daHN d deHN d djHI d doHI d dtHC d dyG~ d d}H@ d eBGy d eGGt d eLGv d eQGt d eUGl d eZGg d e_Gd d edGd d eiGd d enGe d esGd d ewGl d e|Ge d fAG\ d fFGd d fKGe d fOGf d fTGe d fYGb d f^Gb d fcG^ d fhGW d flG\ d fqG` d fvG` d f{G^ d g@G_ d gDG] d gIG\ d gNGc d gSGj d gXGj d g]Gk d gaGi d gfGd d gkGf d gpGd d guG_ d gzG\ d g~G[ d hCGV d hHGS d hMGT d hRGV d hVGV d h[GT d h`GT d heGV d hjGU d hoGS d hsGS d hxGS d h}GU d iBGV d iGGV d iLGV d iPGT d iUGS d iZGQ d i_GQ d idGQ d ihGO d imGP d irGQ d iwGR d i|GR d jAGT d jEGQ d jJGQ d jOGR d jTGL d jYGL d j^GN d jbGI d jgGG d jlGF d jqGH d jvGE d jzGF d j?GG d kDGC d kIGB d kNGC d kSGB d kWGA d k\F? d kaGA d kfGA d kkF? d koF~ d ktF? d kyF~ d k~F} d lCF| d lHFz d lLF~ d lQF? d lVF| d l[Fz d l`Fz d leF| d liF| d lnF~ d lsFw d lxFy d l}Fy m F\Fy d FaFx d FfF{ d FkF| d FoFy d FtF{ d FyFw d F~Fy d GCF| d GHFy d GLF| d GQFy d GVF} d G[F{ d G`F? d GeF| d GiF} d GnF? d GsF? d GxGA d G}GA d HAF~ d HFGB d HKGD d HPGC d HUGE d HZGF d H^GD d HcGE d HhGG d HmGF d HrGD d HwGF d H{GF d I@F{ d IBFw m IFFw d IJG\ d IOGm d ISGQ d IXGE d I]GE d IbGH d IgGF d IlGF d IpGF d IuGF d IzGK d I?GJ d JDGJ d JIGN d JMGM d JRGN d JWGW d J\G] d JaGa d JeG^ d JjGZ d JoG[ d JtG\ d JyGa d J~Gh d KBGr d KGGw d KLGq d KQGh d KVGe d KZGq d K_HC d KdHM d KiHE d KnG~ d KsGw d KwGy d K|G} d LAG? d LFH@ d LKG~ d LPHA d LTG| d LYGy d L^Gr d LcH@ d LhHF d LlHH d LqGy d LvGi d L{G` d M@G^ d MEGY d MIGU d MNGT d MSGS d MXGX d M]G\ d MbG\ d MfG_ d MkG` d MpG[ d MuG\ d MzG` d M~Gc d NCGf d NHGg d NMGe d NRGf d NWGg d N[Gn d N`Gt d NeGn d NjGn d NoGm d NtGo d NxGp d N}Gn d OBGm d OGGi d OLGi d OPGi d OUGf d OZGf d O_Gh d OdGj d OiGn d OmGq d OrGu d OwGy d O|G? d PAHC d PEHC d PJHC d POHD d PTHF d PYHL d P^HQ d PbH] d PgHi d PlHs d PqIB d PvIN d P{IM d P?IK d QDIH d QIIK d QNI[ d QSJ@ d QWJe d Q\JZ d QaIp d QfIP d QkIK d QpIc d QtIt d QyIk d Q~IX d RCID d RHHq d RMHb d RQHY d RVHP d R[HI d R`HC d ReGz d RiGu d RnGt d RsGp d RxGq d R}Gz d SBG? d SFGz d SKGv d SPGt d SUGt d SZGu d S_G~ d ScHB d ShHD d SmHH d SrHE d SwHC d S{G? d T@HC d TEHK d TJHR d TOHR d TTHK d TXHE d T]HC d TbGy d TgG| d TlGw d TpG{ d TuG| d TzGx d T?G{ d UDGt d UIGt d UMGu d URG} d UWHD d U\HQ d UaHS d UfHY d UjHY d UoHd d UtH} d UyID d U~IL d VBIO d VGIV d VLIL d VQIJ d VVI[ d V[If d V_Ip d VdJK d ViJg d VnJS d VsJI d VxI{ d V|I^ d WAIQ d WFIC d WKHx d WPH{ d WTID d WYIC d W^IF d WcIG d WhIP d WmIo d WqJj d WvKv d W{L| d X@M[ d XEMQ d XJMQ d XNMT stroke m XNMT d XSMQ d XXMV d X]M^ d XbMn d XfMl d XkMn d XpMq d XuNa d XzOk d X?PK d YCPI d YHOt d YMOk d YROe d YWOc d Y\O{ d Y`Pj d YeQZ d YjQu d YoQ| d YtQy d YxQz d Y}Q} d ZBRC d ZGRR d ZLRR d ZQQe d ZUP^ d ZZOc d Z_Nu d ZdMy d ZiM@ d ZmMG d ZrMd d ZwMI d Z|L[ d [ALR d [FLR d [JKm d [OKJ d [TJw d [YJ| d [^Km d [cL^ d [gLq d [lLl d [qLG d [vKI d [{Jd d [?JY d \DIw d \IIN d \NHm d \SHh d \XHg d \\Hl d \aHs d \fIF d \kIU d \pIS d \uIH d \yHv d \~H{ d ]CH} d ]HIA d ]MIP d ]QIK d ]VHx d ][Hm d ]`Hg d ]eHY d ]jHU d ]nHU d ]sHN d ]xHK d ]}G~ d ^BGz d ^GG| d ^KG| d ^PGu d ^UG{ d ^ZG~ d ^_Gy d ^cGu d ^hGv d ^mGt d ^rGq d ^wGr d ^|Gv d _@Gu d _EGn d _JGn d _OGo d _TGs d _XGy d _]G? d _bHC d _gHF d _lHI d _qHA d _uG~ d _zHA d _?G| d `DG| d `IGv d `NGs d `RGs d `WGv d `\Gu d `aGt d `fG{ d `jHC d `oHG d `tHN d `yHN d `~HN d aCHS d aGHT d aLHS d aQHS d aVHT d a[HX d a`HY d adH[ d aiH\ d anH\ d asHY d axHQ d a|HN d bAHN d bFHI d bKHC d bPHI d bUHN d bYHS d b^H[ d bcH^ d bhHa d bmHb d brHa d bvHl d b{IB d c@I\ d cEIe d cJI` d cOIV d cSIO d cXIS d c]Ik d cbIv d cgIk d ckI] d cpIS d cuIF d czHx d c?Hl d dDHc d dHHb d dMHf d dRHl d dWHs d d\Hp d daHf d deHY d djHK d doHM d dtH[ d dyHk d d}Hk d eBH[ d eGHT d eLHR d eQHH d eUHA d eZG| d e_Gx d edGs d eiGq d enGk d esGg d ewGf d e|Gf d fAGd d fFGf d fKGb d fOG_ d fTGi d fYGk d f^Gk d fcGn d fhGq d flGv d fqGq d fvGj d f{Gj d g@Gf d gDG_ d gIGZ d gNG^ d gSGa d gXGd d g]Gd d gaGi d gfGm d gkGt d gpG~ d guG| d gzGt d g~Gs d hCGm d hHGi d hMGf d hRGf d hVGh d h[Gd d h`Gc d heG] d hjGT d hoGN d hsGM d hxGT d h}GZ d iBG^ d iGGa d iLGc d iPG_ d iUGY d iZGY d i_GY d idGT d ihGQ d imGP d irGN d iwGO d i|GN d jAGN d jEGL d jJGK d jOGN d jTGN d jYGL d j^GK d jbGL d jgGJ d jlGI d jqGH d jvGI d jzGI d j?GF d kDGF d kIGD d kNGF d kSGC d kWGA d k\GA d kaGA d kfGA d kkF? d koF| d ktF~ d kyF~ d k~F~ d lCF} d lHF? d lLF~ d lQF~ d lVF~ d l[F~ d l`Fz d leF} d liF{ d lnFy d lsF| d lxFy d l}Fy m F\Fy d FaF{ d FfF| d FkF| d FoF~ d FtFy d FyFz d F~Fw d GCFz d GHFz d GLF{ d GQF? d GVF| d G[F~ d G`F{ d GeF} d GiF| d GnF{ d GsF~ d GxF| d G}F~ d HAF~ d HFFz d HKF~ d HPF~ d HUF{ d HZF? d H^F} d HcG@ d HhG@ d HmG@ d HrGA d HwG@ d H{GA d I@GA d IEGC d IJF? d IOGA d ISGB d IXGA d I]GE d IbGE d IgGD d IlGF d IpGG d IuGI d IzGI d I?GF d JDGF d JIGH d JMGJ d JRGL d JWGH d J\GG d JaGO d JeGT d JjGS d JoGU d JtGU d JyGT d J~GT d KBGS d KGGW d KLG] d KQG] d KVG] d KZG[ d K_G\ d KdG^ d KiG[ d KnGY d KsG\ d KwG` d K|Gf d LAGi d LFGe d LKGc d LPG_ d LTG\ d LYGZ d L^GT d LcGU d LhGW d LlGY d LqG] d LvG\ d L{Gc d M@G^ d MEGY d MIGa d MNG\ d MSG^ d MXG\ d M]G] d MbG\ d MfG` d MkG\ d MpG\ d MuGX d MzGZ d M~G^ d NCGU d NHG^ d NMG\ d NRGY d NWG_ d N[G^ d N`G[ d NeG] d NjG\ d NoGZ d NtGV d NxGZ d N}G^ d OBGY d OGGY d OLGW d OPGT d OUGZ d OZGT d O_GZ d OdGV d OiGR d OmGQ d OrGP d OwGS d O|GT d PAGT d PEGT d PJGR d POGT d PTGV d PYGV d P^G[ d PbG[ d PgGY d PlGY d PqG\ d PvGY d P{GZ d P?G^ d QDG_ d QIGb d QNGd d QSGf d QWGd d Q\Gd d QaGi d QfGo d QkGt d QpGv d QtGv d QyGy d Q~G~ d RCHA d RHHD d RMHI d RQHN d RVHV d R[HW d R`HW d ReH] d RiHa d RnHf d RsHj d RxHy d R}IH d SBIW d SFIe d SKIc d SPIh d SUIm d SZIh d S_Ik d ScI| d ShJE d SmJC d SrI? d SwIu d S{Ih d T@I` d TEIU d TJIL d TOIF d TTH| d TXHv d T]Hs d TbHc d TgH_ d TlHU d TpHQ d TuHK d TzHA d T?G~ d UDHA d UIHD d UMHK d URHC d UWHC d U\G? d UaHA d UfH@ d UjG| d UoG| d UtHA d UyHI d U~G| d VBHB d VGHG d VLHF d VQHC d VVHI d V[HQ d V_HS d VdHN d ViHQ d VnH^ d VsHW d VxHW d V|H] d WAHU d WFHP d WKHS d WPHK d WTHL d WYHB d W^HI d WcHD d WhHF d WmHN d WqHO d WvHS d W{HN d X@HR d XEHS d XJHS d XNHV d XSH[ d XXH[ stroke m XXH[ d X]Hv d XbIC d XfHf d XkHI d XpG| d XuG? d XzHA d X?HA d YCHD d YHHI d YMHI d YRHE d YWHC d Y\HF d Y`HJ d YeHH d YjHF d YoHJ d YtHM d YxHJ d Y}HD d ZBHE d ZGHC d ZLG? d ZQGy d ZUGy d ZZG| d Z_G| d ZdHC d ZiG} d ZmGw d ZrGq d ZwGn d Z|Gl d [AGl d [FGn d [JGm d [OGl d [TGo d [YGn d [^Gl d [cGn d [gGw d [lGz d [qHB d [vHK d [{HK d [?HS d \DHJ d \IHF d \NHF d \SHC d \XHF d \\HQ d \aHV d \fHJ d \kHI d \pHI d \uHN d \yHI d \~HA d ]CH@ d ]HG~ d ]MG? d ]QGz d ]VG| d ][Gy d ]`Gs d ]eGv d ]jGq d ]nGx d ]sGn d ]xGv d ]}Gv d ^BGq d ^GGl d ^KGn d ^PGh d ^UGk d ^ZGi d ^_Ga d ^cG_ d ^hGa d ^mGf d ^rGj d ^wGi d ^|Gs d _@Gv d _EGq d _JGn d _OGn d _TGu d _XGq d _]Gk d _bGm d _gGm d _lGh d _qGf d _uGq d _zGp d _?Gp d `DGt d `IGq d `NGs d `RGt d `WGx d `\G} d `aHB d `fHI d `jHL d `oHJ d `tHJ d `yHH d `~HD d aCG| d aGGv d aLGu d aQGv d aVG? d a[HG d a`HI d adHH d aiHI d anHF d asHD d axHI d a|HF d bAHC d bFHB d bKHM d bPHV d bUH[ d bYHa d b^Ha d bcHi d bhHk d bmHl d brHl d bvHi d b{Hn d c@Hn d cEHj d cJHu d cOHl d cSHf d cXH[ d c]H[ d cbHZ d cgHS d ckHK d cpHC d cuGz d czGs d c?Gq d dDGq d dHGv d dMHA d dRHE d dWHD d d\HJ d daHO d deHI d djHC d doG} d dtGp d dyGk d d}Gg d eBGa d eGGb d eLGT d eQG\ d eUGW d eZG\ d e_GX d edG\ d eiGW d enGV d esGV d ewGW d e|GV d fAGV d fFGY d fKG[ d fOGZ d fTGY d fYG\ d f^G^ d fcGY d fhGZ d flGV d fqGZ d fvGY d f{GV d g@GV d gDGT d gIGV d gNGY d gSGX d gXGV d g]GY d gaGY d gfGZ d gkG\ d gpG` d guG^ d gzG] d g~Gb d hCGc d hHG^ d hMG^ d hRGf d hVGo d h[Gm d h`Go d heGk d hjGd d hoG\ d hsGY d hxG[ d h}G\ d iBGV d iGGQ d iLGQ d iPGN d iUGN d iZGS d i_GS d idGS d ihGT d imGW d irG\ d iwG[ d i|G^ d jAG` d jEG\ d jJGY d jOG[ d jTGW d jYGX d j^GY d jbGQ d jgGP d jlGR d jqGO d jvGO d jzGN d j?GM d kDGL d kIGL d kNGL d kSGH d kWGL d k\GK d kaGK d kfGF d kkGD d koGC d ktGA d kyGE d k~GC d lCGA d lHG@ d lLG@ d lQF~ d lVF~ d l[GA d l`GA d leF? d liF~ d lnF? d lsF? d lxF~ d l}G@ stroke /Times-Roman findfont 24 scalefont setfont initmatrix -1 72 mul 300 div 1 72 mul 300 div scale -2409 88 translate 1600 3150 moveto [1 0 0 -1 0 0] concat (NOAO/IRAF sontag@barabbas.stsci.edu Tue Nov 15 15:43:22 2011 ) show showpage ����������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/pset_msstat_input.fits�������������������������������������������������0000644�0001750�0001750�00000402600�14203121554�021232� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������SIMPLE = T / Fits standard BITPIX = 16 / Bits per pixel NAXIS = 0 / Number of axes EXTEND = T / There may be standard extensions ORIGIN = 'NOAO-IRAF FITS Image Kernel July 2003' / FITS file originator IRAF-TLM= '12:06:59 (29/07/2004)' / Time of last modification NEXTEND = 7 / number of extensions in file DATE = '2004-05-29' / date this file was written (yyyy-mm-dd) FILENAME= 'o8u01y050_raw.fits ' / name of file FILETYPE= 'SCI ' / type of data found in data file TELESCOP= 'HST' / telescope used to acquire data INSTRUME= 'STIS ' / identifier for instrument used to acquire data EQUINOX = 2000.0 / equinox of celestial coord. system / DATA DESCRIPTION KEYWORDS ROOTNAME= 'o8u01y050 ' / rootname of the observation setPRIMESI = 'ACS ' / instrument designated as prime / TARGET INFORMATION TARGNAME= 'BIAS ' / proposer's target name RA_TARG = 0.000000000000E+00 / right ascension of the target (deg) (J2000) DEC_TARG= 0.000000000000E+00 / declination of the target (deg) (J2000) / PROPOSAL INFORMATION PROPOSID= 10020 / PEP proposal identifier LINENUM = '1Y.005 ' / proposal logsheet line number PR_INV_L= 'Diaz-Miller ' / last name of principal investigatorPR_INV_F= 'Rosa ' / first name of principal investigator PR_INV_M= ' ' / middle name / initial of principal investigat / SUMMARY EXPOSURE INFORMATION TDATEOBS= '2004-05-25' / UT date of start of first exposure in file TTIMEOBS= '00:31:28' / UT start time of first exposure in file TEXPSTRT= 5.315002185456E+04 / start time (MJD) of 1st exposure in file TEXPEND = 53150.02215567 / end time (MJD) of last exposure in the file TEXPTIME= 0. / total exposure time (seconds) / TARGET OFFSETS (POSTARGS) POSTARG1= 0.000000 / POSTARG in axis 1 direction POSTARG2= 0.000000 / POSTARG in axis 2 direction / DIAGNOSTIC KEYWORDS OVERFLOW= 0 / Number of science data overflows OPUS_VER= 'OPUS 15.0.b0 ' / OPUS software system version number CAL_VER = ' ' / CALSTIS code version PROCTIME= 5.315435967593E+04 / Pipeline processing time (MJD) / SCIENCE INSTRUMENT CONFIGURATION CFSTATUS= 'SUPPORTED ' / configuration status (support., avail., eng.) OBSTYPE = 'IMAGING ' / observation type - imaging or spectroscopic OBSMODE = 'ACCUM ' / operating mode PHOTMODE= ' ' / observation conSCLAMP = 'NONE ' / lamp status, NONE or name of lamp which is on LAMPSET = '0.0 ' / spectral cal lamp current value (milliamps) NRPTEXP = 2 / number of repeat exposures in set: default 1 SUBARRAY= F / data from a subarray (T) or full frame (F) DETECTOR= 'CCD ' / detector in use: NUV-MAMA, FUV-MAMA, or CCD OPT_ELEM= 'MIRVIS ' / optical element in use APERTURE= 'F28X50LP ' / aperture name PROPAPER= 'F28X50LP ' / proposed aperture name FILTER = 'Long_Pass ' / filter in use APER_FOV= '28x50 ' / aperture field of view CRSPLIT = 1 / number of cosmic ray split exposures / ENGINEERING PARAMETERS CCDAMP = 'D ' / CCD amplifier read out (A,B,C,D) CCDGAIN = 1 / commanded gain of CCD CCDOFFST= 3 / commanded CCD bias offset / READOUT DEFINITION PARAMETERS CENTERA1= 532 / subarray axis1 center pt in unbinned dect. pix CENTERA2= 523 / subarray axis2 center pt in unbinned dect. pix SIZAXIS1= 1062 / subarray axis1 size in unbinned detector pixelsSIZAXIS2= 1044 / subarray axis2 size in unbinned detector pixelsBINAXIS1= 2 / axis1 data bin size in unbinned detector pixelsBINAXIS2= 2 / axis2 data bin size in unbinned detector pixels / PHOTOMETRY KEYWORDS PHOTFLAM= 0.000000000000E+00 / inverse sensitivity, ergs/s/cm2/Ang per count/sPHOTZPT = 0.000000 / ST magnitude zero point PHOTPLAM= 0.000000 / Pivot wavelength (Angstroms) PHOTBW = 0.000000 / RMS bandwidth of filter plus detector / CALIBRATION SWITCHES: PERFORM, OMIT, COMPLETE DQICORR = 'PERFORM ' / data quality initialization ATODCORR= 'OMIT ' / correct for A to D conversion errors BLEVCORR= 'PERFORM ' / subtract bias level computed from overscan img BIASCORR= 'OMIT ' / Subtract bias image CRCORR = 'OMIT ' / combine observations to reject cosmic rays RPTCORR = 'OMIT ' / add individual repeat observations EXPSCORR= 'OMIT ' / process individual observations after cr-rejectDARKCORR= 'perform ' / Subtract dark image FLATCORR= 'OMIT ' / flat field data SHADCORR= 'OMIT ' / apply shutter shading correction PHOTCORR= 'OMIT ' / populate photometric header keywords STATFLAG= T / Calculate statistics? GEOCORR = 'OMIT ' / perform geometric correction for imaging modes / CALIBRATION REFERENCE FILES BPIXTAB = 'oref$h1v11475o_bpx.fits' / bad pixel table DARKFILE= 'oref$o5p1609fo_drk.fits' / dark image file name PFLTFILE= 'oref$h4s1351lo_pfl.fits' / pixel to pixel flat field file name DFLTFILE= 'N/A ' / delta flat field file name LFLTFILE= 'oref$jaj1058ho_lfl.fits' / low order flat PHOTTAB = 'oref$l7a15023o_pht.fits' / Photometric throughput table APERTAB = 'oref$n7p1032ao_apt.fits' / relative aperture throughput table CCDTAB = 'july29b_ccd.fits' / CCD calibration parameters ATODTAB = 'N/A ' / analog to digital correction file BIASFILE= 'oref$o5k0942bo_bia.fits' / bias image file name SHADFILE= 'N/A ' / shutter shading correction file CRREJTAB= 'oref$j3m1403io_crr.fits' / cosmic ray rejection parameters IDCTAB = 'oref$l8g1208eo_idc.fits' / image distortion correction table TDSTAB = 'oref$o4817264o_tds.fits' / time-dependent sensitivity algorithm used / COSMIC RAY REJECTION ALGORITHM PARAMETERS MEANEXP = 0.000000 / reference exposure time for parameters SCALENSE= 0.000000 / multiplicative scale factor applied to noise INITGUES= ' ' / initial guess method (MIN or MED) SKYSUB = ' ' / sky value subtracted (MODE or NONE) SKYSUM = 0.0 / sky level from the sum of all constituent imageCRSIGMAS= ' ' / statistical rejection criteria CRRADIUS= 0.000000 / rejection propagation radius (pixels) CRTHRESH= 0.000000 / rejection propagation threshold BADINPDQ= 0 / data quality flag bits to reject REJ_RATE= 0.0 / rate at which pixels are affected by cosmic rayCRMASK = F / flag CR-rejected pixels in input files (T/F) / CALIBRATED ENGINEERING PARAMETERS ATODGAIN= 0.000000 / calibrated CCD amplifier gain value READNSE = 0.000000 / calibrated CCD read noise value / TARGET ACQUISITION DATASET IDENTIFIERS ACQNAME = ' ' / rootname of acquisition exposure ACQTYPE = ' ' / type of acquisition PEAKNAM1= ' ' / rootname of 1st peakup exposure PEAKNAM2= ' ' / rootname of 2nd peakup exposure / PATTERN KEYWORDS PATTERN1= 'NONE ' / primary pattern type P1_SHAPE= ' ' / primary pattern shape P1_PURPS= ' ' / primary pattern purpose P1_NPTS = 0 / number of points in primary pattern P1_PSPAC= 0.000000 / point spacing for primary pattern (arc-sec) P1_LSPAC= 0.000000 / line spacing for primary pattern (arc-sec) P1_ANGLE= 0.000000 / angle between sides of parallelogram patt (deg)P1_FRAME= ' ' / coordinate frame of primary pattern P1_ORINT= 0.000000 / orientation of pattern to coordinate frame (degP1_CENTR= ' ' / center pattern relative to pointing (yes/no) / ARCHIVE SEARCH KEYWORDS BANDWID = 1092.0 / bandwidth of the data SPECRES = 0.0 / approx. resolving power at central wavelength CENTRWV = 7150.0 / central wavelength of the data MINWAVE = 5490.0 / minimum wavelength in spectrum MAXWAVE = 9990.0 / maximum wavelength in spectrum PLATESC = 0.101554 / plate scale (arcsec/pixel) / PAPER PRODUCT SUPPORT KEYWORDS PROPTTL1= 'CCD Bias Monitor - Part 2 'OBSET_ID= '1Y' / observation set id MTFLAG = ' ' / moving target flag; T if it is a moving target PARALLAX= 0.000000000000E+00 / target parallax from proposal MU_RA = 0.000000000000E+00 / target proper motion from proposal (degrees RA)MU_DEC = 0.000000000000E+00 / target proper motion from proposal (deg. DEC) MU_EPOCH= ' ' / epoch of proper motion from proposal / ASSOCIATION KEYWORDS ASN_ID = 'O8U01Y050 ' / unique identifier assigned to association ASN_TAB = 'o8u01y050_asn.fits ' / name of the association table END XTENSION= 'IMAGE ' / Image extension BITPIX = 16 / Bits per pixel NAXIS = 2 / Number of axes NAXIS1 = 128 / Axis length NAXIS2 = 128 / Axis length PCOUNT = 0 / No 'random' parameters GCOUNT = 1 / Only one group BSCALE = 1 / REAL = TAPE*BSCALE + BZERO BZERO = 32768 EXTNAME = 'SCI ' / Extension name EXTVER = 1 / Extension version ORIGIN = 'NOAO-IRAF FITS Image Kernel July 2003' / FITS file originator DATE = '2004-06-08T16:33:37' / Date FITS file was generated IRAF-TLM= '16:32:07 (08/06/2004)' / Time of last modification INHERIT = T / inherit the primary header EXPNAME = 'o8u01yfvq ' / exposure identifier DATAMIN = 1116. / the minimum value of the data DATAMAX = 16144. / the maximum value of the data BUNIT = 'COUNTS ' / brightness units ASN_MTYP= 'REPEATOBS ' / Role of the Member in the Association / World Coordinate System and Related Parameters WCSAXES = 2 / number of World Coordinate System axes CRPIX1 = 268.942 / x-coordinate of reference pixel CRPIX2 = 268.585 / y-coordinate of reference pixel CRVAL1 = 1.613199727623E+02 / first axis value at reference pixel CRVAL2 = 2.135475501191E+01 / second axis value at reference pixel CTYPE1 = 'RA---TAN' / the coordinate type for the first axis CTYPE2 = 'DEC--TAN' / the coordinate type for the second axis CD1_1 = -1.24968E-05 / partial of first axis coordinate w.r.t. x CD1_2 = 2.52887E-05 / partial of first axis coordinate w.r.t. y CD2_1 = 2.52817E-05 / partial of second axis coordinate w.r.t. x CD2_2 = 1.25002E-05 / partial of second axis coordinate w.r.t. y LTV1 = 10.75 / offset in X to subsection start LTV2 = 10.25 / offset in Y to subsection start LTM1_1 = 0.5 / reciprocal of sampling rate in X LTM2_2 = 0.5 / reciprocal of sampling rate in Y RA_APER = 1.612768019682E+02 / RA of aperture reference position DEC_APER= 2.143102718354E+01 / Declination of aperture reference position PA_APER = -7.135916080322E+01 / Position Angle of reference aperture center (de / EXPOSURE INFORMATION ORIENTAT= 63.6967 / position angle of image y axis (deg. e of n) SUNANGLE= 90.453804 / angle between sun and V1 axis MOONANGL= 27.510509 / angle between moon and V1 axis SUN_ALT = 22.079735 / altitude of the sun above Earth's limb FGSLOCK = 'GYROS ' / commanded FGS lock (FINE,COARSE,GYROS,UNKNOWN) DATE-OBS= '2004-05-25' / UT date of start of observation (yyyy-mm-dd) TIME-OBS= '00:31:28' / UT time of start of observation (hh:mm:ss) EXPSTART= 5.315002185456E+04 / exposure start time (Modified Julian Date) EXPEND = 5.315002185456E+04 / exposure end time (Modified Julian Date) EXPTIME = 0.000000 / exposure duration (seconds)--calculated EXPFLAG = 'NORMAL ' / Exposure interruption indicator / PATTERN KEYWORDS PATTSTEP= 0 / position number of this point in the pattern / REPEATED EXPOSURES INFO NCOMBINE= 1 / number of image sets combined during CR rejecti / DATA PACKET INFORMATION FILLCNT = 0 / number of segments containing fill ERRCNT = 0 / number of segments containing errors PODPSFF = F / podps fill present (T/F) STDCFFF = F / ST DDF fill present (T/F) STDCFFP = '0x5569' / ST DDF fill pattern (hex) / ENGINEERING PARAMETERS OSWABSP = 3824312 / Slit Wheel Absolute position OMSCYL1P= 866 / Mode select cylinder 1 position OMSCYL3P= 4921 / Mode select cylinder 3 position OMSCYL4P= 2739 / Mode select cylinder 4 position OCBABAV = 26.6827 / (V) CEB A&B Amp Bias OCBCDAV = 26.7221 / (V) CEB C&D amp bias OCBLGCDV= -3.38383 / (V) CEB last gate C&D OCBSWALV= -5.99128 / (V) CB summing well A Lo OCBRCDLV= 0.0438864 / (V) CB reset gate CD Lo OCCDHTAV= 20.192300 / average CCD housing temperature (degC) / IMAGE STATISTICS AND DATA QUALITY FLAGS NGOODPIX= 277704 / number of good pixels SDQFLAGS= 31743 / serious data quality flags GOODMIN = 1116.0 / minimum value of good pixels GOODMAX = 16144.0 / maximum value of good pixels GOODMEAN= 1158.768799 / mean value of good pixels SNRMIN = 0.000000 / minimum signal to noise of good pixels SNRMAX = 0.000000 / maximum signal to noise of good pixels SNRMEAN = 0.000000 / mean value of signal to noise of good pixels SOFTERRS= 0 / number of soft error pixels (DQF=1) MEANDARK= 0.0 / average of the dark values subtracted MEANBLEV= 0.0 / average of all bias levels subtracted END ~~}y|}|~y~x~}w~x|}|~~~~~|}}z|~||Ʉ}~|{}||~~~}}~}}z|~}}~~~{}}}|~~|}}}|~~y}~~y~y~}}|}}||~|{w}|x|Ȅ~{~~}}||}z}}~|Ą~xy~~~}}||}~~}~}|}|~}}Ƅ~|~{|~}z~|}}}~}|~~~~}}~y~~|}~~|{}}x}|~~~~||}~~~}}||}}|}~}}||}~}|~Qل}z|}ń~}}|΄~y}|~}~~}}}||{|~~}y}~}}}~~}~|y}}~~y~焑~|~~~w~|}~{{}~~||~}}~~}||}~~~|}}|}{x|y~|}{}y|||y|}~}̄~}~|~y}~|}|~~}||}}~}}~}~~~}~}~~~~{̄|}}w}~}ńz~}}}~~|}|}}}|}~|}z||~~~}}}||}|~~}}~Ą}}}}~~|}|zy}~~|}~}}}|}}{}}}~|~|}yy}|~~}yx}}z~}}~}|~~}}}{~}}~|{|}~|~~~|}}}z}|}~|~~~}|}}~y}|||~|~~~{|y|}|~{}}}}}~|}~}}|~}{{~}~}~yzz}}}~~}~}{~}|}}|y}~|}}{~~}~|~~}}y}~~}~~~}~~}||{}~~~~~~|}||~}y~|z~}}}Ä~|~~}}}z}y}~~wz~}}||~|~~yz}}}~}z~~}{|}~}}~~|~w}{}{}w~~y~~}}||~zz~}w}|~~~}~}~}|}|}}~{y|}|}}}}|}~{|{}yx}w||}~}}~~|}~|~}~~~}|~}{}~~y~{|~}|||{}~~|}|z|}~|}x}}|~}|{||~~}~|~~x~~|}~����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������XTENSION= 'IMAGE ' / extension type BITPIX = 16 / bits per data value NAXIS = 0 / number of data axes PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups INHERIT = T / inherit the primary header EXTNAME = 'ERR ' / extension name EXTVER = 1 / extension version number ROOTNAME= 'o8u01y050 ' / rootname of the observation setEXPNAME = 'o8u01yfvq ' / exposure identifier DATAMIN = 0.0 / the minimum value of the data DATAMAX = 0.0 / the maximum value of the data BUNIT = 'COUNTS ' / brightness units NPIX1 = 532 / length of constant array axis 1 NPIX2 = 522 / length of constant array axis 2 PIXVALUE= 0.0 / values of pixels in constant array / World Coordinate System and Related Parameters WCSAXES = 2 / number of World Coordinate System axes CRPIX1 = 268.942 / x-coordinate of reference pixel CRPIX2 = 268.585 / y-coordinate of reference pixel CRVAL1 = 1.613199727623E+02 / first axis value at reference pixel CRVAL2 = 2.135475501191E+01 / second axis value at reference pixel CTYPE1 = 'RA---TAN' / the coordinate type for the first axis CTYPE2 = 'DEC--TAN' / the coordinate type for the second axis CD1_1 = -1.24968E-05 / partial of first axis coordinate w.r.t. x CD1_2 = 2.52887E-05 / partial of first axis coordinate w.r.t. y CD2_1 = 2.52817E-05 / partial of second axis coordinate w.r.t. x CD2_2 = 1.25002E-05 / partial of second axis coordinate w.r.t. y LTV1 = 10.75 / offset in X to subsection start LTV2 = 10.25 / offset in Y to subsection start LTM1_1 = 0.5 / reciprocal of sampling rate in X LTM2_2 = 0.5 / reciprocal of sampling rate in Y RA_APER = 1.612768019682E+02 / RA of aperture reference position DEC_APER= 2.143102718354E+01 / Declination of aperture reference position PA_APER = -7.135916080322E+01 / Position Angle of reference aperture center (de / NOISE MODEL KEYWORDS NOISEMOD= ' ' / noise model equation NOISCOF1= 0.000000000000E+00 / noise coefficient 1 NOISCOF2= 0.000000000000E+00 / noise coefficient 2 NOISCOF3= 0.000000000000E+00 / noise coefficient 3 NOISCOF4= 0.000000000000E+00 / noise coefficient 4 NOISCOF5= 0.000000000000E+00 / noise coefficient 5 / IMAGE STATISTICS AND DATA QUALITY FLAGS NGOODPIX= 277704 / number of good pixels SDQFLAGS= 31743 / serious data quality flags GOODMIN = 1116.0 / minimum value of good pixels GOODMAX = 16144.0 / maximum value of good pixels GOODMEAN= 1158.768799 / mean value of good pixels END XTENSION= 'IMAGE ' / extension type BITPIX = 16 / bits per data value NAXIS = 0 / number of data axes PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups INHERIT = T / inherit the primary header EXTNAME = 'DQ ' / extension name EXTVER = 1 / extension version number ROOTNAME= 'o8u01y050 ' / rootname of the observation setEXPNAME = 'o8u01yfvq ' / exposure identifier DATAMIN = 0.0 / the minimum value of the data DATAMAX = 0.0 / the maximum value of the data BUNIT = 'UNITLESS ' / brightness units NPIX1 = 532 / length of constant array axis 1 NPIX2 = 522 / length of constant array axis 2 PIXVALUE= 0 / values of pixels in constant array / World Coordinate System and Related Parameters WCSAXES = 2 / number of World Coordinate System axes CRPIX1 = 268.942 / x-coordinate of reference pixel CRPIX2 = 268.585 / y-coordinate of reference pixel CRVAL1 = 1.613199727623E+02 / first axis value at reference pixel CRVAL2 = 2.135475501191E+01 / second axis value at reference pixel CTYPE1 = 'RA---TAN' / the coordinate type for the first axis CTYPE2 = 'DEC--TAN' / the coordinate type for the second axis CD1_1 = -1.24968E-05 / partial of first axis coordinate w.r.t. x CD1_2 = 2.52887E-05 / partial of first axis coordinate w.r.t. y CD2_1 = 2.52817E-05 / partial of second axis coordinate w.r.t. x CD2_2 = 1.25002E-05 / partial of second axis coordinate w.r.t. y LTV1 = 10.75 / offset in X to subsection start LTV2 = 10.25 / offset in Y to subsection start LTM1_1 = 0.5 / reciprocal of sampling rate in X LTM2_2 = 0.5 / reciprocal of sampling rate in Y RA_APER = 1.612768019682E+02 / RA of aperture reference position DEC_APER= 2.143102718354E+01 / Declination of aperture reference position PA_APER = -7.135916080322E+01 / Position Angle of reference aperture center (deEND XTENSION= 'IMAGE ' / Image extension BITPIX = 16 / Bits per pixel NAXIS = 2 / Number of axes NAXIS1 = 128 / Axis length NAXIS2 = 128 / Axis length PCOUNT = 0 / No 'random' parameters GCOUNT = 1 / Only one group BSCALE = 1 / REAL = TAPE*BSCALE + BZERO BZERO = 32768 EXTNAME = 'SCI ' / Extension name EXTVER = 2 / Extension version ORIGIN = 'NOAO-IRAF FITS Image Kernel July 2003' / FITS file originator DATE = '2004-06-08T20:32:34' / Date FITS file was generated IRAF-TLM= '16:32:15 (08/06/2004)' / Time of last modification INHERIT = T / inherit the primary header EXPNAME = 'o8u01yfwq ' / exposure identifier DATAMIN = 1116. / the minimum value of the data DATAMAX = 14022. / the maximum value of the data BUNIT = 'COUNTS ' / brightness units ASN_MTYP= 'REPEATOBS ' / Role of the Member in the Association / World Coordinate System and Related Parameters WCSAXES = 2 / number of World Coordinate System axes CRPIX1 = 268.942 / x-coordinate of reference pixel CRPIX2 = 268.585 / y-coordinate of reference pixel CRVAL1 = 1.613199727623E+02 / first axis value at reference pixel CRVAL2 = 2.135475501191E+01 / second axis value at reference pixel CTYPE1 = 'RA---TAN' / the coordinate type for the first axis CTYPE2 = 'DEC--TAN' / the coordinate type for the second axis CD1_1 = -1.24968E-05 / partial of first axis coordinate w.r.t. x CD1_2 = 2.52887E-05 / partial of first axis coordinate w.r.t. y CD2_1 = 2.52817E-05 / partial of second axis coordinate w.r.t. x CD2_2 = 1.25002E-05 / partial of second axis coordinate w.r.t. y LTV1 = 10.75 / offset in X to subsection start LTV2 = 10.25 / offset in Y to subsection start LTM1_1 = 0.5 / reciprocal of sampling rate in X LTM2_2 = 0.5 / reciprocal of sampling rate in Y RA_APER = 1.612768019682E+02 / RA of aperture reference position DEC_APER= 2.143102718354E+01 / Declination of aperture reference position PA_APER = -7.135916080322E+01 / Position Angle of reference aperture center (de / EXPOSURE INFORMATION ORIENTAT= 63.6967 / position angle of image y axis (deg. e of n) SUNANGLE= 90.453522 / angle between sun and V1 axis MOONANGL= 27.507029 / angle between moon and V1 axis SUN_ALT = 23.702038 / altitude of the sun above Earth's limb FGSLOCK = 'GYROS ' / commanded FGS lock (FINE,COARSE,GYROS,UNKNOWN) DATE-OBS= '2004-05-25' / UT date of start of observation (yyyy-mm-dd) TIME-OBS= '00:31:54' / UT time of start of observation (hh:mm:ss) EXPSTART= 5.315002215567E+04 / exposure start time (Modified Julian Date) EXPEND = 5.315002215567E+04 / exposure end time (Modified Julian Date) EXPTIME = 0.000000 / exposure duration (seconds)--calculated EXPFLAG = 'NORMAL ' / Exposure interruption indicator / PATTERN KEYWORDS PATTSTEP= 0 / position number of this point in the pattern / REPEATED EXPOSURES INFO NCOMBINE= 1 / number of image sets combined during CR rejecti / DATA PACKET INFORMATION FILLCNT = 0 / number of segments containing fill ERRCNT = 0 / number of segments containing errors PODPSFF = F / podps fill present (T/F) STDCFFF = F / ST DDF fill present (T/F) STDCFFP = '0x5569' / ST DDF fill pattern (hex) / ENGINEERING PARAMETERS OSWABSP = 3824311 / Slit Wheel Absolute position OMSCYL1P= 866 / Mode select cylinder 1 position OMSCYL3P= 4921 / Mode select cylinder 3 position OMSCYL4P= 2739 / Mode select cylinder 4 position OCBABAV = 26.6827 / (V) CEB A&B Amp Bias OCBCDAV = 26.7221 / (V) CEB C&D amp bias OCBLGCDV= -3.38383 / (V) CEB last gate C&D OCBSWALV= -5.99614 / (V) CB summing well A Lo OCBRCDLV= 0.0487692 / (V) CB reset gate CD Lo OCCDHTAV= 20.192300 / average CCD housing temperature (degC) / IMAGE STATISTICS AND DATA QUALITY FLAGS NGOODPIX= 277704 / number of good pixels SDQFLAGS= 31743 / serious data quality flags GOODMIN = 1116.0 / minimum value of good pixels GOODMAX = 14022.0 / maximum value of good pixels GOODMEAN= 1157.404785 / mean value of good pixels SNRMIN = 0.000000 / minimum signal to noise of good pixels SNRMAX = 0.000000 / maximum signal to noise of good pixels SNRMEAN = 0.000000 / mean value of signal to noise of good pixels SOFTERRS= 0 / number of soft error pixels (DQF=1) MEANDARK= 0.0 / average of the dark values subtracted MEANBLEV= 0.0 / average of all bias levels subtracted END ~|~~~~}~}|~}y~}}}|}{}|}~}~}|}x|~}}|~~}~}~~|}}~}{}y}}~~w}}||}|}}z|~|{}}y}}}~~}~~}|y~~~~z|z}}{|~{~z}}}~~~{~}||}~~|~}|~~~~|{}~}}~~~|~~}Ņ x}|}||6|~~~~~u~||}~Ʉ|}x~~y{}yz~~~z|~}|}~}}}}}~|{}~}|}~}}||}~||}}}zxz~|~~}~Ä}~}z~x|}y~|}{~~z|}~||||Ȅ||||}~|~}}~x~}|}~}|}}~~~}}|}|}{}|΄~x}~~z}~xz{}}|}~|}|}|}|~||}}||{|}}|y~z{}yy|}y}~~{||}~|}|}}|~{}}|}~~||„~}}~y}||z|}}}~}}y~||}{{{}~{|{}|}}|}}~|~||~}~~}{~~~~~~|{}{~{~{|}}~|z}~~}~}}~|y|}}~}~|~~}~}w{}|||~}{~|~}~~||}~}}}{~<~~|~~}|}|~~}|}}~}}{}~}}zz}|Ʉ~ᄍ}}Ą}y}~}||~|zz~~}{|}~}||~|}|}||}}}~~}~z}}}y||~y|~~}}}|}}}|~~}}~}|~~yz}}z~~~}~}~~|}}}}||~~|}|{}~|~}z~}}~|}~||~|||}}|~~}}}~}}|x~}~}}}y}~}|}|~}}}y|}~}|w~~~|~{x~}~~|~|}|yz~~}{}~}|~~~}|}}~|}{}|}~}|~}~~}{|}~}|}~|~}}}~|}~}}~}|~}~~~{y~~~{}y}}~{|x}|||}~}}|~~{~z}|}y~~|{}|||~}~|}|~|z}}}}~|}}}}z}~|}}}~~}}}}|}}{z}}}~{}~{}}}y}||}}~}||}~||~xw}w~}y}}z}~y}}~||}|||x{}zz~}~}}{|}~~~„}}}}~z{|~||z}|}|~||~~~|}����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������XTENSION= 'IMAGE ' / extension type BITPIX = 16 / bits per data value NAXIS = 0 / number of data axes PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups INHERIT = T / inherit the primary header EXTNAME = 'ERR ' / extension name EXTVER = 2 / extension version number ROOTNAME= 'o8u01y050 ' / rootname of the observation setEXPNAME = 'o8u01yfwq ' / exposure identifier DATAMIN = 0.0 / the minimum value of the data DATAMAX = 0.0 / the maximum value of the data BUNIT = 'COUNTS ' / brightness units NPIX1 = 532 / length of constant array axis 1 NPIX2 = 522 / length of constant array axis 2 PIXVALUE= 0.0 / values of pixels in constant array / World Coordinate System and Related Parameters WCSAXES = 2 / number of World Coordinate System axes CRPIX1 = 268.942 / x-coordinate of reference pixel CRPIX2 = 268.585 / y-coordinate of reference pixel CRVAL1 = 1.613199727623E+02 / first axis value at reference pixel CRVAL2 = 2.135475501191E+01 / second axis value at reference pixel CTYPE1 = 'RA---TAN' / the coordinate type for the first axis CTYPE2 = 'DEC--TAN' / the coordinate type for the second axis CD1_1 = -1.24968E-05 / partial of first axis coordinate w.r.t. x CD1_2 = 2.52887E-05 / partial of first axis coordinate w.r.t. y CD2_1 = 2.52817E-05 / partial of second axis coordinate w.r.t. x CD2_2 = 1.25002E-05 / partial of second axis coordinate w.r.t. y LTV1 = 10.75 / offset in X to subsection start LTV2 = 10.25 / offset in Y to subsection start LTM1_1 = 0.5 / reciprocal of sampling rate in X LTM2_2 = 0.5 / reciprocal of sampling rate in Y RA_APER = 1.612768019682E+02 / RA of aperture reference position DEC_APER= 2.143102718354E+01 / Declination of aperture reference position PA_APER = -7.135916080322E+01 / Position Angle of reference aperture center (de / NOISE MODEL KEYWORDS NOISEMOD= ' ' / noise model equation NOISCOF1= 0.000000000000E+00 / noise coefficient 1 NOISCOF2= 0.000000000000E+00 / noise coefficient 2 NOISCOF3= 0.000000000000E+00 / noise coefficient 3 NOISCOF4= 0.000000000000E+00 / noise coefficient 4 NOISCOF5= 0.000000000000E+00 / noise coefficient 5 / IMAGE STATISTICS AND DATA QUALITY FLAGS NGOODPIX= 277704 / number of good pixels SDQFLAGS= 31743 / serious data quality flags GOODMIN = 1116.0 / minimum value of good pixels GOODMAX = 14022.0 / maximum value of good pixels GOODMEAN= 1157.404785 / mean value of good pixels END XTENSION= 'IMAGE ' / extension type BITPIX = 16 / bits per data value NAXIS = 0 / number of data axes PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups INHERIT = T / inherit the primary header EXTNAME = 'DQ ' / extension name EXTVER = 2 / extension version number ROOTNAME= 'o8u01y050 ' / rootname of the observation setEXPNAME = 'o8u01yfwq ' / exposure identifier DATAMIN = 0.0 / the minimum value of the data DATAMAX = 0.0 / the maximum value of the data BUNIT = 'UNITLESS ' / brightness units NPIX1 = 532 / length of constant array axis 1 NPIX2 = 522 / length of constant array axis 2 PIXVALUE= 0 / values of pixels in constant array / World Coordinate System and Related Parameters WCSAXES = 2 / number of World Coordinate System axes CRPIX1 = 268.942 / x-coordinate of reference pixel CRPIX2 = 268.585 / y-coordinate of reference pixel CRVAL1 = 1.613199727623E+02 / first axis value at reference pixel CRVAL2 = 2.135475501191E+01 / second axis value at reference pixel CTYPE1 = 'RA---TAN' / the coordinate type for the first axis CTYPE2 = 'DEC--TAN' / the coordinate type for the second axis CD1_1 = -1.24968E-05 / partial of first axis coordinate w.r.t. x CD1_2 = 2.52887E-05 / partial of first axis coordinate w.r.t. y CD2_1 = 2.52817E-05 / partial of second axis coordinate w.r.t. x CD2_2 = 1.25002E-05 / partial of second axis coordinate w.r.t. y LTV1 = 10.75 / offset in X to subsection start LTV2 = 10.25 / offset in Y to subsection start LTM1_1 = 0.5 / reciprocal of sampling rate in X LTM2_2 = 0.5 / reciprocal of sampling rate in Y RA_APER = 1.612768019682E+02 / RA of aperture reference position DEC_APER= 2.143102718354E+01 / Declination of aperture reference position PA_APER = -7.135916080322E+01 / Position Angle of reference aperture center (deEND XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / 8-bit bytes NAXIS = 2 / 2-dimensional binary table NAXIS1 = 4 / width of table in bytes NAXIS2 = 1 PCOUNT = 0 / size of special data area GCOUNT = 1 / one data group (required keyword) TFIELDS = 1 TTYPE1 = 'acol ' / label for field 1 TFORM1 = '1E ' / data format of field: 4-byte REAL TUNIT1 = 'help ' / physical unit of field TDISP1 = 'F4.4 ' / display format END ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psi_land_prow_250.ps���������������������������������������������������0000644�0001750�0001750�00000044103�14203121554�020350� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-1.0 %%Creator: PSIKern - An IRAF GKI Translator %%CreationDate: Tue 15:17:23 21-Aug-2007 %%DocumentFonts: (atend) %%Pages: (atend) %%BoundingBox: 2 2 557 737 %%EndComments /MAXNDC 4095 def /PageSizeXMeter 0.2594 def /PageSizeYMeter 0.1959 def /PageOffXMeter 0.001 def /PageOffYMeter 0.001 def /PortraitMode false def /PortraitRotation 0. def /LandScapeRotation 90. def /GT_LEFT 2 def /GT_RIGHT 3 def /GT_TOP 6 def /GT_BOTTOM 7 def /GT_UP 4 def /GT_DOWN 5 def /GT_CENTER 1 def /RF /Times-Roman def /GF /Symbol def /IF /Times-Italic def /BF /Times-Bold def /DASH 49 def /DOT 4 def /SPACE 24 def % How to define things. /BD { bind def } bind def /LD { load def } bind def % Constants /yes true def /no false def % Define some shorthand instructions. /GRS /grestore LD /GS /gsave LD /MK /mark LD /R /restore LD /S /save LD /SH /showpage LD /SW /setlinewidth LD % Measurement conversions. /PointperMeter 2834.64 def /MonoScale .7 def % PDF mark definition to avoid errors in printing Postscript /pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse /languagelevel where {pop languagelevel }{1} ifelse 2 lt { userdict (<<) cvn ([) cvn load put userdict (>>) cvn (]) cvn load put } if % SR: Set current path to page: - -> - /DG true store /SR { DG {1 setlinecap} {0 setlinecap} ifelse currentpoint stroke moveto /DG true store } BD % CL: CLear the page: - -> - /CL { restore showpage grestore } BD % rectfill : Fill a rectangle: x y width height -> - /rectfill where {pop} {/rectfill { gsave newpath 4 2 roll moveto exch dup 0 rlineto exch 0 exch rlineto neg 0 rlineto closepath fill grestore } BD } ifelse % SetBack: Set the background: /SetBack { /BACKCOLOR where { pop BACKCOLOR SC 0 0 MAXNDC MAXNDC rectfill } if } BD % NP: Start a new page: /NP { gsave save SetBack } BD % RC: Read Coordinates: string -> x y /RC { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } BD % DO - Determine Offset: length -> offset /DO { DS dup 0 eq { pop pop 0 } { mod } ifelse } BD % M: Move: M string -> - % Move to a point and clear length of path. /CX 0 def /CY 0 def /M { RC 2 copy /CY exch def /CX exch def moveto CT OF setdash } BD % D: Draw: D string -> - % This also calculates the length of the current path. /OF 0 def /D { RC DS 0 ne { 2 copy currentpoint exch 4 1 roll sub dup mul 3 1 roll sub dup mul add sqrt round OF add cvi DO /OF exch store } if DG { 2 copy CY eq exch CX eq and /DG exch def } if lineto } BD % Character/Font definitions. % FS -- Define the current font size: xsize ysize FS - /FontXSize 0 def /FontYSize 0 def /NewFont true def /FS { /FontYSize exch MonoScale mul store /FontXSize exch MonoScale mul store /NewFont true store } BD % PH -- Set the current path: path PH - /Path 0 def /PH { /Path exch store } BD % PA -- Set the path angle of the text: angle PA - /Angle 0 def /PA { 360 mod dup 0 lt { 360 add } if /Angle exch store /NewFont true store } BD % HJ, VJ -- Set Horizontal/Verticle Justification: just HJ/VJ - /HorzJust 0 def /VertJust 0 def /HJ { /HorzJust exch store } BD /VJ { /VertJust exch store } BD % VT -- Use mono- or variable-spaced fonts: flag VT - /VT true def /VT { /Variable exch store } BD % SetFont -- Make the required font the default: font SetFont - % Note scale factor- This is to fill out monospaced better and match other % IRAF output. /Font () def /FontMatrix matrix def /SetFont { dup Font ne NewFont or { dup /Font exch store findfont Angle matrix rotate FontXSize MonoScale div FontYSize MonoScale div matrix scale matrix concatmatrix makefont setfont Angle matrix rotate FontXSize FontYSize matrix scale matrix concatmatrix /FontMatrix exch store } { pop } ifelse /NewFont false store } BD % StringWidth -- Determine path length of string: string StringWidth - xs ys /StringWidth { Path GT_RIGHT eq Variable and { stringwidth } { Path GT_RIGHT eq { length } { pop 1 } ifelse 0 FontMatrix transform } ifelse } BD % MonoShow -- Write the string out in mono-spaced: string MonoShow - /MonoShow { /t 1 string def 1 0 FontMatrix transform /dy exch def /dx exch def Path GT_RIGHT eq { /mx dx def /my dy def } { 0 1 FontMatrix transform /my exch def /mx exch def } ifelse { t 0 3 -1 roll put t dup stringwidth dy exch sub 2 div exch dx exch sub 2 div exch gsave rmoveto show grestore mx my rmoveto } forall } BD % WS -- Draw the string: <mark> string font string ... DS - /*WSDict 13 dict def /WS { *WSDict begin counttomark 2 idiv dup dup array /FontArray exch def array /StringArray exch def 1 sub /NStrings exch def NStrings -1 0 { dup 4 1 roll exch StringArray 3 1 roll put FontArray 3 1 roll put } for pop /XSize 0 def /YSize 0 def /NChars 0 def 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get dup length NChars add /NChars exch def StringWidth YSize add /YSize exch def XSize add /XSize exch def } for /TAngle HorzJust GT_RIGHT eq { Angle 180 add dup 360 ge { 360 sub } if } { Angle } ifelse def 0 TAngle 180 le HorzJust GT_CENTER eq or { Angle sin FontYSize mul Path GT_UP eq { NChars mul } if add } if TAngle 90 ge TAngle 270 le and HorzJust GT_CENTER eq or { Path GT_RIGHT eq { XSize } { Angle cos FontXSize mul } ifelse sub } if HorzJust GT_CENTER eq { .5 mul } if /TAngle VertJust GT_TOP eq { Angle 180 sub dup 0 lt { 360 add } if } { Angle } ifelse def 0 TAngle 90 ge TAngle 270 le and VertJust GT_CENTER eq or { Angle cos FontYSize mul Path GT_UP eq { NChars mul } if sub } if TAngle 180 ge TAngle 360 le and VertJust GT_CENTER eq or { Path GT_RIGHT eq { YSize } { Angle sin FontXSize mul } ifelse sub } if VertJust GT_CENTER eq { .5 mul } if rmoveto 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get Path GT_RIGHT eq Variable and { show } { MonoShow } ifelse } for end } BD % Define the DASHED, DOTTED, DOTDASH patterns. /DS 0 def /LPS { /OF 0 store /DS 0 store 0 array } BD /LPD { /OF 0 store DASH SPACE 2 copy add /DS exch store 2 array astore } BD /LPP { /OF 0 store DOT SPACE 2 copy add /DS exch store 2 array astore } BD /LDD { /OF 0 store DOT SPACE DASH SPACE 4 copy add add add /DS exch store 4 array astore } BD % LT - Set Line Type - array -> - /CT { currentdash pop } BD /LT { /CT exch store /OF 0 store } BD % SC - Set Color: color -> - /*SCDict 1 dict def /SC { *SCDict begin /color exch def GR color get 255 div GG color get 255 div GB color get 255 div setrgbcolor end } BD % MI - Make Image LUT: table-name size MI hexstring -> - /MI { currentfile exch string readhexstring pop def } BD % Define the graphics color lookup table. /GR 16 MI FF00FF0000FF00FFFFB0FFF0D940F0F5 /GG 16 MI FF0000FF00FFFF0080D0A6E670E082DE /GB 16 MI FF000000FF00FFFF4F61008CD6D1EFB3 % Render an image when no image LUT has been define. /DefaultGrey { { currentfile inarr readhexstring pop } image } BD % ColorImage - Produce an image by indexing into the Image Lookup Table. % There are two versions of this code- one for PostScript that has % the colorimage operator and one that doesn't. If the PostScript doesn't % have colorimage, it is assumed that it can only produce black & white, % in which the Image LUT is combined to produce a single grey which is % then used as the image for the image operator. /colorimage where { pop /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length 3 mul string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def /oindex iindex 3 mul def oarr oindex IR inarr iindex get get put oarr oindex 1 add IG inarr iindex get get put oarr oindex 2 add IB inarr iindex get get put } for oarr } false 3 colorimage } BD } { /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def oarr iindex IR inarr iindex get get 0.3 mul IG inarr iindex get get 0.59 mul IB inarr iindex get get 0.11 mul add add round cvi dup 255 gt { pop 255 } if put } for oarr } image } BD } ifelse % PC: Put Cellarray: width height bitspersample matrix -> - /*PCdict 10 dict def /PC { *PCdict begin 4 -1 roll dup /inarr exch string def 4 1 roll /IR where {pop ColorImage} {DefaultGrey} ifelse end } BD % Define the Hatch (fill area) styles. H1 and H2 are handled internally % by PSIKern. Feel free to define more; there is no limit. /H3 <8888888888888888> def /H4 <ff000000ff000000> def /H5 <8844221188442211> def /H6 <1122448811224488> def % Setup for fill patterns. This whole section of code is taken almost % directly from _PostScript Language: Tutorial and Cookbook_, Adobe Systems, % 1986 (the blue book). /setuserscreendict 22 dict def setuserscreendict begin /tempctm matrix def /temprot matrix def /tempscale matrix def /concatprocs { /proc2 exch cvlit def /proc1 exch cvlit def /newproc proc1 length proc2 length add array def newproc 0 proc1 putinterval newproc proc1 length proc2 putinterval newproc cvx } def /resmatrix matrix def /findresolution { 72 0 resmatrix defaultmatrix dtransform /yres exch def /xres exch def xres dup mul yres dup mul add sqrt } def end /setuserscreen { setuserscreendict begin /spotfunction exch def /screenangle exch def /cellsize exch def /m tempctm currentmatrix def /rm screenangle temprot rotate def /sm cellsize dup tempscale scale def sm rm m m concatmatrix m concatmatrix pop 1 0 m dtransform /y1 exch def /x1 exch def /veclength x1 dup mul y1 dup mul add sqrt def /frequency findresolution veclength div def /newscreenangle y1 x1 atan def m 2 get m 1 get mul m 0 get m 3 get mul sub 0 gt { {neg} /spotfunction load concatprocs /spotfunction exch def } if frequency newscreenangle /spotfunction load setscreen end } def /setpatterndict 18 dict def setpatterndict begin /bitison { /ybit exch def /xbit exch def /bytevalue bstring ybit bwidth mul xbit 8 idiv add get def /mask 1 7 xbit 8 mod sub bitshift def bytevalue mask and 0 ne } def end /bitpatternspotfunction { setpatterndict begin /y exch def /x exch def /xindex x 1 add 2 div bpside mul cvi def /yindex y 1 add 2 div bpside mul cvi def xindex yindex bitison { /onbits onbits 1 add def 1 } { /offbits offbits 1 add def 0 } ifelse end } def /setpattern { setpatterndict begin /cellsz exch def /angle exch def /bwidth exch def /bpside exch def /bstring exch def /onbits 0 def /offbits 0 def cellsz angle /bitpatternspotfunction load setuserscreen { } settransfer offbits offbits onbits add div setgray end } def % SP: Set Pattern: pattern -> - /SP { 8 1 0 MAXNDC 100 div setpattern } BD % FI: Fill with Pattern: - -> - /FI { fill grestore } BD % Set the transformation matrix. /SS { PointperMeter dup scale PageOffXMeter PortraitMode not { PageSizeYMeter add } if PageOffYMeter translate PortraitMode { PortraitRotation rotate } { LandScapeRotation rotate} ifelse PageSizeXMeter MAXNDC div PageSizeYMeter MAXNDC div scale 1 setlinejoin 0 setlinecap } BD % Save the current state of VM S /GR 16 MI FF00FF000000FFFFA02DFFEFD83FEDF4 /GG 16 MI FF0000FF00FFFF00214FA5E570E082DD /GB 16 MI FF000000FFFF00FFEF4F008CD6D1EDB2 SS S %%EndProlog %PSKOpenWorkStation %%Page: 1 1 NP 9 SC GS M @@@@ D ??@@ D ???? D @@?? FI 0 SC GS M KaH] D t]H] D t]|f D Ka|f FI LPS LT 7 SW 5 SC M KaH] D MBH] D MBIH D MBH] D NiH] D NiIH D NiH] D POH] D POIH D POH] D QvH] D QvIH D QvH] D S\H] D S\Is D S\H] SR 0 PA 81.85751 108.2269 FS 3 PH 1 HJ 6 VJ yes VT 6 SC M S\Gg MK BF (100) WS 5 SC M S\H] D UCH] D UCIH D UCH] D ViH] D ViIH D ViH] D XPH] D XPIH D XPH] D YwH] D YwIH D YwH] D []H] D []Is D []H] SR 6 SC M []Gg MK BF (200) WS 5 SC M []H] D ]DH] D ]DIH D ]DH] D ^jH] D ^jIH D ^jH] D `QH] D `QIH D `QH] D awH] D awIH D awH] D c^H] D c^Is D c^H] SR 6 SC M c^Gg MK BF (300) WS 5 SC M c^H] D eDH] D eDIH D eDH] D fkH] D fkIH D fkH] D hRH] D hRIH D hRH] D ixH] D ixIH D ixH] D k_H] D k_Is D k_H] SR 6 SC M k_Gg MK BF (400) WS 5 SC M k_H] D mEH] D mEIH D mEH] D nlH] D nlIH D nlH] D pSH] D pSIH D pSH] D qyH] D qyIH D qyH] D s`H] D s`Is D s`H] SR 6 SC M s`Gg MK BF (500) WS 5 SC M s`H] D t]H] SR M t]H] D t]IX D s}IX D t]IX D t]K| D s}K| D t]K| D t]N` D s}N` D t]N` D t]QC D s}QC D t]QC D t]Sg D s\Sg D t]Sg D t]VK D s}VK D t]VK D t]Xo D s}Xo D t]Xo D t][S D s}[S D t][S D t]]w D s}]w D t]]w D t]`[ D s\`[ D t]`[ D t]b? D s}b? D t]b? D t]ec D s}ec D t]ec D t]hG D s}hG D t]hG D t]jk D s}jk D t]jk D t]mN D s\mN D t]mN D t]or D s}or D t]or D t]rV D s}rV D t]rV D t]tz D s}tz D t]tz D t]w^ D s}w^ D t]w^ D t]zB D s\zB D t]zB D t]|f D s}|f D t]|f D t]|f SR M KaH] D KaIX D LAIX D KaIX D KaK| D LAK| D KaK| D KaN` D LAN` D KaN` D KaQC D LAQC D KaQC D KaSg D LaSg D KaSg SR 3 HJ 1 VJ 6 SC M JxSg MK BF (250) WS 5 SC M KaSg D KaVK D LAVK D KaVK D KaXo D LAXo D KaXo D Ka[S D LA[S D Ka[S D Ka]w D LA]w D Ka]w D Ka`[ D La`[ D Ka`[ SR 6 SC M Jx`[ MK BF (500) WS 5 SC M Ka`[ D Kab? D LAb? D Kab? D Kaec D LAec D Kaec D KahG D LAhG D KahG D Kajk D LAjk D Kajk D KamN D LamN D KamN SR 6 SC M JxmO MK BF (750) WS 5 SC M KamN D Kaor D LAor D Kaor D KarV D LArV D KarV D Katz D LAtz D Katz D Kaw^ D LAw^ D Kaw^ D KazB D LazB D KazB SR 6 SC M JxzB MK BF (1000) WS 5 SC M KazB D Ka|f D LA|f D Ka|f D Ka|f SR M Ka|f D MB|f D MB{{ D MB|f D Ni|f D Ni{{ D Ni|f D PO|f D PO{{ D PO|f D Qv|f D Qv{{ D Qv|f D S\|f D S\{P D S\|f D UC|f D UC{{ D UC|f D Vi|f D Vi{{ D Vi|f D XP|f D XP{{ D XP|f D Yw|f D Yw{{ D Yw|f D []|f D []{P D []|f D ]D|f D ]D{{ D ]D|f D ^j|f D ^j{{ D ^j|f D `Q|f D `Q{{ D `Q|f D aw|f D aw{{ D aw|f D c^|f D c^{P D c^|f D eD|f D eD{{ D eD|f D fk|f D fk{{ D fk|f D hR|f D hR{{ D hR|f D ix|f D ix{{ D ix|f D k_|f D k_{P D k_|f D mE|f D mE{{ D mE|f D nl|f D nl{{ D nl|f D pS|f D pS{{ D pS|f D qy|f D qy{{ D qy|f D s`|f D s`{P D s`|f D t]|f SR 1 HJ 3 SC M _?DN MK BF (Column \(pixels\)) WS 7 VJ M _?}\ MK BF (dev$pix: row 250) WS 3 SW 1 SC M KaIG D KfID D KkIQ D KpIT D KuIG D KzIQ D K?IA D LDIK D LJIT D LOIK D LTIT D LYIK D L^I[ D LcIQ D LhIe D LmIX D LsI[ D LxIe D L}Ie D MBIo D MGIo D MLIa D MQIr D MVI| D M\Iu D MaI? D MfJB D MkIx D MpI? D MuJI D MzJB D N@I| D NEJB D NJJB D NOIQ D NTH] D NYKe D N^Ls D NcJw D NiI? D NnI? D NsJL D NxJB D N}JF D OBJB D OGJB D OLJY D ORJV D OWJV D O\Jj D OaJc D OfJj D OkKQ D OpKk D OvK? D O{Kn D P@K^ D PEKa D PJKh D POK? D PTL\ D PYMJ D P_Ma D PdMD D PiL\ D PnLO D PsMD D PxNV D P}OD D QBN` D QHM? D QMMa D QRMh D QWM{ D Q\NE D QaNI D QfM? D QkNL D QqMx D QvMh D Q{MJ D R@NI D RENc D RJNm D ROMh D RTL` D RZKx D R_Kn D RdKX D RiKG D RnKA D RsJ} D RxKT D R}Ke D SCKh D SHKu D SMKx D SRKa D SWKh D S\Kx D SaLE D SfLS D SlLY D SqLO D SvLS D S{LY D T@Lz D TEMT D TJLz D TPLz D TULs D TZL} D T_M@ D TdLw D TiLs D TnLc D TsL` D TyL` D T~LV D UCLV D UHL\ D UMLf D URLz D UWMG D U\MW D UbMh D UgNE D UlNV D UqNY D UvNY D U{N\ D V@Nf D VEO@ D VKOW D VPPL D VUQ@ D VZQn D V_Rs D VdSk D ViSd D VoSZ D VtSM D VySZ D V~Tb D WCWM D WHYt D WMY@ D WRVE D WXSt D W]S] D WbUJ D WgVU D WlUn D WqTU D WvR} D W{Qh D XAPc D XFOx D XKOQ D XPNp D XUNV D XZMn D X_MW D XeMQ D XjM@ D XoMG D XtMn D XyNE D X~Mn D YCM[ D YHMT D YNMT D YSMW D YXM? D Y]NR D YbN\ D YgNm D YlN` D YqNV D YwNE D Y|NV D ZANz D ZFO[ D ZKO[ D ZPN} D ZUN` D ZZNV D Z`Mk D ZeMu D ZjMa D ZoMr D ZtMu D ZyMe D Z~Mr D [DMT D [IMT D [NMW D [SM{ D [XN\ D []OT D [bOa D [gO{ D [lO{ D [rPm D [wR\ D [|R} D \ASa D \FSn D \KTK D \PSa D \USW D \[Tb D \`UW D \eVA D \jW~ D \oY{ D \tXb D \yWt D \?Vs D ]DTs D ]ISx D ]NRy D ]SRH D ]XRU D ]]R} D ]bRv D ]hSG D ]mSJ D ]rSt D ]wU~ D ]|ZH D ^A_` D ^Fd[ D ^Kfg D ^Qe} D ^Ve} D ^[fJ D ^`ez D ^efQ D ^jfu D ^oh@ D ^tgs D ^zg} D ^?hM D _Dkf D _Ipt D _NsD D _Srz D _Xq[ D _^pt D _cpV D _hpP D _mqy D _ruQ D _wxi D _|zc D `A{D D `Gzs D `Lzv D `Q{G D `V{a D `[|f D ``|f D `ey[ D `jtY D `ppP D `ul~ D `zhq D `?dn D aDeL D aIgO D aNeY D aTbG D aYa` D a^a` D ac^u D ah\[ D am[C D ar[Z D aw^y D a}bT D bBci D bGcV D bL`o D bQ\U D bVYm D b[X| D b`Vb D bfSk D bkQT D bpP} D buPz D bzQQ D b?Qq D cDSG D cITH D cOS~ D cTSP D cYQ{ D c^RR D ccR\ D chRp D cmSt D csSZ D cxRH D c}QT D dBPz D dGO{ D dLOh D dQOh D dVOJ D d\Nz D daM? D dfMn D dkMx D dpMu D duMW D dzMr D d?NB D eDMk D eJMW D eOM[ D eTMT D eYMD D e^MJ D ecM^ D ehMW D enLz D esLz D exL} D e}MN D fBMk D fGNE D fLNY D fQNf D fWNs D f\NO D faNB D ffNL D fkMu D fpMu D fuM[ D fzMN D g@MN D gEM[ D gJMW D gOMQ D gTMr D gYNV D g^Ni D gdOG D giOG D gnOG D gsOa D gxOd D g}Oa D hBO^ D hGOd D hMOu D hRO{ D hWPE D h\PH D haPH D hfOx D hkOW D hpOJ D hvOG D h{Np D i@NV D iENp D iJOG D iOOa D iTPE D iYPO D i_P_ D idPc D iiP\ D inQQ D isRs D ixTi D i}US D jCTy D jHTO D jMSn D jRS~ D jWUn D j\V_ D jaUn D jfTl D jlS~ D jqSC D jvRH D j{QQ D k@Pf D kEPc D kJPs D kOQQ D kUQq D kZQa D k_Ps D kdOx D kiNz D knOD D ksPE D kyQJ D k~QJ D lCPE D lHOd D lMO[ D lRNm D lWNL D l\Mx D lbMe D lgMN D llMD D lqLi D lvLY D l{LV D m@LS D mELL D mKLS D mPLB D mUKu D mZL` D m_Li D mdLi D miLz D mnMD D msM^ D myMD D m~Lf D nCLf D nHLS D nMKu D nRK^ D nWKn D n]K| D nbLI D ngLL D nlLc D nqLs D nvMT D n{NB D o@Mu D oFMQ D oKMN D oPLs D oUL` D oZLV D o_LS D odL\ D oiLL D ooLE D otKk D oyKA D o~Jj D pCJc D pHKD D pMK^ D pSKn D pXK| D p]LE D pbKu D pgKX D plK[ D pqKX D pvKA D p|Jw D qAJp D qFJf D qKJm D qPJj D qUJj D qZJ` D q_JY D qeJf D qjJj D qoJ` D qtJY D qyJ` D q~JV D rCJS D rHJL D rNJO D rSJS D rXJB D r]JB D rbIx D rgJB D rlIu D rrIo D rwIo D r|Io D sAIk D sFIe D sKIX D sPI^ D sUI^ D s[I^ D s`I[ D seIe D sjIa SR D soI^ D stIa D syIa D s~IN D tDI[ D tIIQ D tNIK D tSIX D tXIG D t]IK SR %PSKCloseWorkStation %PSKOpenWorkStation CL %%Trailer R R %DocumentFonts: Times-Bold Times-Italic Symbol Times-Roman %%Pages: 1 %%EOF �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psi_land_prow_256.ps���������������������������������������������������0000644�0001750�0001750�00000044506�14203121554�020365� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-1.0 %%Creator: PSIKern - An IRAF GKI Translator %%CreationDate: Mon 17:14:24 10-Sep-2007 %%DocumentFonts: (atend) %%Pages: (atend) %%BoundingBox: 2 2 557 737 %%EndComments /MAXNDC 4095 def /PageSizeXMeter 0.2594 def /PageSizeYMeter 0.1959 def /PageOffXMeter 0.001 def /PageOffYMeter 0.001 def /PortraitMode false def /PortraitRotation 0. def /LandScapeRotation 90. def /GT_LEFT 2 def /GT_RIGHT 3 def /GT_TOP 6 def /GT_BOTTOM 7 def /GT_UP 4 def /GT_DOWN 5 def /GT_CENTER 1 def /RF /Times-Roman def /GF /Symbol def /IF /Times-Italic def /BF /Times-Bold def /DASH 49 def /DOT 4 def /SPACE 24 def % How to define things. /BD { bind def } bind def /LD { load def } bind def % Constants /yes true def /no false def % Define some shorthand instructions. /GRS /grestore LD /GS /gsave LD /MK /mark LD /R /restore LD /S /save LD /SH /showpage LD /SW /setlinewidth LD % Measurement conversions. /PointperMeter 2834.64 def /MonoScale .7 def % PDF mark definition to avoid errors in printing Postscript /pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse /languagelevel where {pop languagelevel }{1} ifelse 2 lt { userdict (<<) cvn ([) cvn load put userdict (>>) cvn (]) cvn load put } if % SR: Set current path to page: - -> - /DG true store /SR { DG {1 setlinecap} {0 setlinecap} ifelse currentpoint stroke moveto /DG true store } BD % CL: CLear the page: - -> - /CL { restore showpage grestore } BD % rectfill : Fill a rectangle: x y width height -> - /rectfill where {pop} {/rectfill { gsave newpath 4 2 roll moveto exch dup 0 rlineto exch 0 exch rlineto neg 0 rlineto closepath fill grestore } BD } ifelse % SetBack: Set the background: /SetBack { /BACKCOLOR where { pop BACKCOLOR SC 0 0 MAXNDC MAXNDC rectfill } if } BD % NP: Start a new page: /NP { gsave save SetBack } BD % RC: Read Coordinates: string -> x y /RC { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } BD % DO - Determine Offset: length -> offset /DO { DS dup 0 eq { pop pop 0 } { mod } ifelse } BD % M: Move: M string -> - % Move to a point and clear length of path. /CX 0 def /CY 0 def /M { RC 2 copy /CY exch def /CX exch def moveto CT OF setdash } BD % D: Draw: D string -> - % This also calculates the length of the current path. /OF 0 def /D { RC DS 0 ne { 2 copy currentpoint exch 4 1 roll sub dup mul 3 1 roll sub dup mul add sqrt round OF add cvi DO /OF exch store } if DG { 2 copy CY eq exch CX eq and /DG exch def } if lineto } BD % Character/Font definitions. % FS -- Define the current font size: xsize ysize FS - /FontXSize 0 def /FontYSize 0 def /NewFont true def /FS { /FontYSize exch MonoScale mul store /FontXSize exch MonoScale mul store /NewFont true store } BD % PH -- Set the current path: path PH - /Path 0 def /PH { /Path exch store } BD % PA -- Set the path angle of the text: angle PA - /Angle 0 def /PA { 360 mod dup 0 lt { 360 add } if /Angle exch store /NewFont true store } BD % HJ, VJ -- Set Horizontal/Verticle Justification: just HJ/VJ - /HorzJust 0 def /VertJust 0 def /HJ { /HorzJust exch store } BD /VJ { /VertJust exch store } BD % VT -- Use mono- or variable-spaced fonts: flag VT - /VT true def /VT { /Variable exch store } BD % SetFont -- Make the required font the default: font SetFont - % Note scale factor- This is to fill out monospaced better and match other % IRAF output. /Font () def /FontMatrix matrix def /SetFont { dup Font ne NewFont or { dup /Font exch store findfont Angle matrix rotate FontXSize MonoScale div FontYSize MonoScale div matrix scale matrix concatmatrix makefont setfont Angle matrix rotate FontXSize FontYSize matrix scale matrix concatmatrix /FontMatrix exch store } { pop } ifelse /NewFont false store } BD % StringWidth -- Determine path length of string: string StringWidth - xs ys /StringWidth { Path GT_RIGHT eq Variable and { stringwidth } { Path GT_RIGHT eq { length } { pop 1 } ifelse 0 FontMatrix transform } ifelse } BD % MonoShow -- Write the string out in mono-spaced: string MonoShow - /MonoShow { /t 1 string def 1 0 FontMatrix transform /dy exch def /dx exch def Path GT_RIGHT eq { /mx dx def /my dy def } { 0 1 FontMatrix transform /my exch def /mx exch def } ifelse { t 0 3 -1 roll put t dup stringwidth dy exch sub 2 div exch dx exch sub 2 div exch gsave rmoveto show grestore mx my rmoveto } forall } BD % WS -- Draw the string: <mark> string font string ... DS - /*WSDict 13 dict def /WS { *WSDict begin counttomark 2 idiv dup dup array /FontArray exch def array /StringArray exch def 1 sub /NStrings exch def NStrings -1 0 { dup 4 1 roll exch StringArray 3 1 roll put FontArray 3 1 roll put } for pop /XSize 0 def /YSize 0 def /NChars 0 def 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get dup length NChars add /NChars exch def StringWidth YSize add /YSize exch def XSize add /XSize exch def } for /TAngle HorzJust GT_RIGHT eq { Angle 180 add dup 360 ge { 360 sub } if } { Angle } ifelse def 0 TAngle 180 le HorzJust GT_CENTER eq or { Angle sin FontYSize mul Path GT_UP eq { NChars mul } if add } if TAngle 90 ge TAngle 270 le and HorzJust GT_CENTER eq or { Path GT_RIGHT eq { XSize } { Angle cos FontXSize mul } ifelse sub } if HorzJust GT_CENTER eq { .5 mul } if /TAngle VertJust GT_TOP eq { Angle 180 sub dup 0 lt { 360 add } if } { Angle } ifelse def 0 TAngle 90 ge TAngle 270 le and VertJust GT_CENTER eq or { Angle cos FontYSize mul Path GT_UP eq { NChars mul } if sub } if TAngle 180 ge TAngle 360 le and VertJust GT_CENTER eq or { Path GT_RIGHT eq { YSize } { Angle sin FontXSize mul } ifelse sub } if VertJust GT_CENTER eq { .5 mul } if rmoveto 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get Path GT_RIGHT eq Variable and { show } { MonoShow } ifelse } for end } BD % Define the DASHED, DOTTED, DOTDASH patterns. /DS 0 def /LPS { /OF 0 store /DS 0 store 0 array } BD /LPD { /OF 0 store DASH SPACE 2 copy add /DS exch store 2 array astore } BD /LPP { /OF 0 store DOT SPACE 2 copy add /DS exch store 2 array astore } BD /LDD { /OF 0 store DOT SPACE DASH SPACE 4 copy add add add /DS exch store 4 array astore } BD % LT - Set Line Type - array -> - /CT { currentdash pop } BD /LT { /CT exch store /OF 0 store } BD % SC - Set Color: color -> - /*SCDict 1 dict def /SC { *SCDict begin /color exch def GR color get 255 div GG color get 255 div GB color get 255 div setrgbcolor end } BD % MI - Make Image LUT: table-name size MI hexstring -> - /MI { currentfile exch string readhexstring pop def } BD % Define the graphics color lookup table. /GR 16 MI FF00FF0000FF00FFFFB0FFF0D940F0F5 /GG 16 MI FF0000FF00FFFF0080D0A6E670E082DE /GB 16 MI FF000000FF00FFFF4F61008CD6D1EFB3 % Render an image when no image LUT has been define. /DefaultGrey { { currentfile inarr readhexstring pop } image } BD % ColorImage - Produce an image by indexing into the Image Lookup Table. % There are two versions of this code- one for PostScript that has % the colorimage operator and one that doesn't. If the PostScript doesn't % have colorimage, it is assumed that it can only produce black & white, % in which the Image LUT is combined to produce a single grey which is % then used as the image for the image operator. /colorimage where { pop /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length 3 mul string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def /oindex iindex 3 mul def oarr oindex IR inarr iindex get get put oarr oindex 1 add IG inarr iindex get get put oarr oindex 2 add IB inarr iindex get get put } for oarr } false 3 colorimage } BD } { /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def oarr iindex IR inarr iindex get get 0.3 mul IG inarr iindex get get 0.59 mul IB inarr iindex get get 0.11 mul add add round cvi dup 255 gt { pop 255 } if put } for oarr } image } BD } ifelse % PC: Put Cellarray: width height bitspersample matrix -> - /*PCdict 10 dict def /PC { *PCdict begin 4 -1 roll dup /inarr exch string def 4 1 roll /IR where {pop ColorImage} {DefaultGrey} ifelse end } BD % Define the Hatch (fill area) styles. H1 and H2 are handled internally % by PSIKern. Feel free to define more; there is no limit. /H3 <8888888888888888> def /H4 <ff000000ff000000> def /H5 <8844221188442211> def /H6 <1122448811224488> def % Setup for fill patterns. This whole section of code is taken almost % directly from _PostScript Language: Tutorial and Cookbook_, Adobe Systems, % 1986 (the blue book). /setuserscreendict 22 dict def setuserscreendict begin /tempctm matrix def /temprot matrix def /tempscale matrix def /concatprocs { /proc2 exch cvlit def /proc1 exch cvlit def /newproc proc1 length proc2 length add array def newproc 0 proc1 putinterval newproc proc1 length proc2 putinterval newproc cvx } def /resmatrix matrix def /findresolution { 72 0 resmatrix defaultmatrix dtransform /yres exch def /xres exch def xres dup mul yres dup mul add sqrt } def end /setuserscreen { setuserscreendict begin /spotfunction exch def /screenangle exch def /cellsize exch def /m tempctm currentmatrix def /rm screenangle temprot rotate def /sm cellsize dup tempscale scale def sm rm m m concatmatrix m concatmatrix pop 1 0 m dtransform /y1 exch def /x1 exch def /veclength x1 dup mul y1 dup mul add sqrt def /frequency findresolution veclength div def /newscreenangle y1 x1 atan def m 2 get m 1 get mul m 0 get m 3 get mul sub 0 gt { {neg} /spotfunction load concatprocs /spotfunction exch def } if frequency newscreenangle /spotfunction load setscreen end } def /setpatterndict 18 dict def setpatterndict begin /bitison { /ybit exch def /xbit exch def /bytevalue bstring ybit bwidth mul xbit 8 idiv add get def /mask 1 7 xbit 8 mod sub bitshift def bytevalue mask and 0 ne } def end /bitpatternspotfunction { setpatterndict begin /y exch def /x exch def /xindex x 1 add 2 div bpside mul cvi def /yindex y 1 add 2 div bpside mul cvi def xindex yindex bitison { /onbits onbits 1 add def 1 } { /offbits offbits 1 add def 0 } ifelse end } def /setpattern { setpatterndict begin /cellsz exch def /angle exch def /bwidth exch def /bpside exch def /bstring exch def /onbits 0 def /offbits 0 def cellsz angle /bitpatternspotfunction load setuserscreen { } settransfer offbits offbits onbits add div setgray end } def % SP: Set Pattern: pattern -> - /SP { 8 1 0 MAXNDC 100 div setpattern } BD % FI: Fill with Pattern: - -> - /FI { fill grestore } BD % Set the transformation matrix. /SS { PointperMeter dup scale PageOffXMeter PortraitMode not { PageSizeYMeter add } if PageOffYMeter translate PortraitMode { PortraitRotation rotate } { LandScapeRotation rotate} ifelse PageSizeXMeter MAXNDC div PageSizeYMeter MAXNDC div scale 1 setlinejoin 0 setlinecap } BD % Save the current state of VM S /GR 16 MI FF00FF000000FFFFA02DFFEFD83FEDF4 /GG 16 MI FF0000FF00FFFF00214FA5E570E082DD /GB 16 MI FF000000FFFF00FFEF4F008CD6D1EDB2 SS S %%EndProlog %PSKOpenWorkStation %%Page: 1 1 NP 9 SC GS M @@@@ D ??@@ D ???? D @@?? FI 0 SC GS M KaH] D t]H] D t]|f D Ka|f FI LPS LT 7 SW 5 SC M KaH] D MBH] D MBIH D MBH] D NiH] D NiIH D NiH] D POH] D POIH D POH] D QvH] D QvIH D QvH] D S\H] D S\Is D S\H] SR 0 PA 81.85751 108.2269 FS 3 PH 1 HJ 6 VJ yes VT 6 SC M S\Gg MK BF (100) WS 5 SC M S\H] D UCH] D UCIH D UCH] D ViH] D ViIH D ViH] D XPH] D XPIH D XPH] D YwH] D YwIH D YwH] D []H] D []Is D []H] SR 6 SC M []Gg MK BF (200) WS 5 SC M []H] D ]DH] D ]DIH D ]DH] D ^jH] D ^jIH D ^jH] D `QH] D `QIH D `QH] D awH] D awIH D awH] D c^H] D c^Is D c^H] SR 6 SC M c^Gg MK BF (300) WS 5 SC M c^H] D eDH] D eDIH D eDH] D fkH] D fkIH D fkH] D hRH] D hRIH D hRH] D ixH] D ixIH D ixH] D k_H] D k_Is D k_H] SR 6 SC M k_Gg MK BF (400) WS 5 SC M k_H] D mEH] D mEIH D mEH] D nlH] D nlIH D nlH] D pSH] D pSIH D pSH] D qyH] D qyIH D qyH] D s`H] D s`Is D s`H] SR 6 SC M s`Gg MK BF (500) WS 5 SC M s`H] D t]H] SR M t]H] D t]If D s}If D t]If D t]Kf D s}Kf D t]Kf D t]Mf D s}Mf D t]Mf D t]Of D s}Of D t]Of D t]Qf D s\Qf D t]Qf D t]Sf D s}Sf D t]Sf D t]Ug D s}Ug D t]Ug D t]Wg D s}Wg D t]Wg D t]Yg D s}Yg D t]Yg D t][g D s\[g D t][g D t]]g D s}]g D t]]g D t]_g D s}_g D t]_g D t]ah D s}ah D t]ah D t]ch D s}ch D t]ch D t]eh D s\eh D t]eh D t]gh D s}gh D t]gh D t]ih D s}ih D t]ih D t]kh D s}kh D t]kh D t]mi D s}mi D t]mi D t]oi D s\oi D t]oi D t]qi D s}qi D t]qi D t]si D s}si D t]si D t]ui D s}ui D t]ui D t]wi D s}wi D t]wi D t]yi D s\yi D t]yi D t]{j D s}{j D t]{j D t]|f SR M KaH] D KaIf D LAIf D KaIf D KaKf D LAKf D KaKf D KaMf D LAMf D KaMf D KaOf D LAOf D KaOf D KaQf D LaQf D KaQf SR 3 HJ 1 VJ 6 SC M JxQf MK BF (500) WS 5 SC M KaQf D KaSf D LASf D KaSf D KaUg D LAUg D KaUg D KaWg D LAWg D KaWg D KaYg D LAYg D KaYg D Ka[g D La[g D Ka[g SR 6 SC M Jx[g MK BF (1000) WS 5 SC M Ka[g D Ka]g D LA]g D Ka]g D Ka_g D LA_g D Ka_g D Kaah D LAah D Kaah D Kach D LAch D Kach D Kaeh D Laeh D Kaeh SR 6 SC M Jxeh MK BF (1500) WS 5 SC M Kaeh D Kagh D LAgh D Kagh D Kaih D LAih D Kaih D Kakh D LAkh D Kakh D Kami D LAmi D Kami D Kaoi D Laoi D Kaoi SR 6 SC M Jxoi MK BF (2000) WS 5 SC M Kaoi D Kaqi D LAqi D Kaqi D Kasi D LAsi D Kasi D Kaui D LAui D Kaui D Kawi D LAwi D Kawi D Kayi D Layi D Kayi SR 6 SC M Jxyj MK BF (2500) WS 5 SC M Kayi D Ka{j D LA{j D Ka{j D Ka|f SR M Ka|f D MB|f D MB{{ D MB|f D Ni|f D Ni{{ D Ni|f D PO|f D PO{{ D PO|f D Qv|f D Qv{{ D Qv|f D S\|f D S\{P D S\|f D UC|f D UC{{ D UC|f D Vi|f D Vi{{ D Vi|f D XP|f D XP{{ D XP|f D Yw|f D Yw{{ D Yw|f D []|f D []{P D []|f D ]D|f D ]D{{ D ]D|f D ^j|f D ^j{{ D ^j|f D `Q|f D `Q{{ D `Q|f D aw|f D aw{{ D aw|f D c^|f D c^{P D c^|f D eD|f D eD{{ D eD|f D fk|f D fk{{ D fk|f D hR|f D hR{{ D hR|f D ix|f D ix{{ D ix|f D k_|f D k_{P D k_|f D mE|f D mE{{ D mE|f D nl|f D nl{{ D nl|f D pS|f D pS{{ D pS|f D qy|f D qy{{ D qy|f D s`|f D s`{P D s`|f D t]|f SR 1 HJ 3 SC M _?DN MK BF (Column \(pixels\)) WS 7 VJ M _?}\ MK BF (dev$pix: row 256) WS 3 SW 1 SC M KaHc D KfHd D KkHd D KpHd D KuHc D KzHf D K?Ha D LDHh D LJHf D LOHa D LTHf D LYHd D L^Hb D LcHh D LhHf D LmHg D LsHh D LxHo D L}Hk D MBHo D MGHt D MLHm D MQHp D MVHm D M\Ht D MaHo D MfHq D MkHt D MpHu D MuHq D MzHu D N@Hs D NEHq D NJHu D NOHx D NTHt D NYHx D N^Hz D NcHu D NiHt D NnHv D NsHy D NxHt D N}Hu D OBH} D OGIC D OLIB D ORIF D OWID D O\IA D OaID D OfII D OkIF D OpIG D OvIQ D O{IO D P@IR D PEIY D PJIY D POIb D PTIj D PYIl D P_Iq D PdI} D PiIv D PnIo D PsIh D PxId D P}Id D QBIl D QHIx D QMJG D QRJB D QWI} D Q\Iz D QaJA D QfI} D QkIs D QqIk D QvIo D Q{Io D R@Io D REIh D RJIj D ROIj D RTIm D RZIj D R_Id D RdId D RiI_ D RnIY D RsIZ D RxI[ D R}IU D SCI^ D SHI_ D SMIc D SRI^ D SWId D S\Ia D SaIa D SfIl D SlIx D SqIt D SvI~ D S{Iu D T@I{ D TEI{ D TJIy D TPIt D TUIv D TZIt D T_Is D TdIp D TiIu D TnI~ D TsI? D TyI? D T~I~ D UCJJ D UHJJ D UMJS D URJP D UWJJ D U\JK D UbJC D UgJG D UlJD D UqJB D UvJM D U{JX D V@Jd D VEJt D VKJ? D VPKT D VUKb D VZKb D V_Ke D VdKo D ViKy D VoKz D VtLB D VyLG D V~LT D WCLl D WHLx D WMLj D WRLT D WXKy D W]Kb D WbKP D WgKC D WlJ~ D WqJs D WvJf D W{J\ D XAJZ D XFJ\ D XKJX D XPJY D XUJU D XZJP D X_JS D XeJP D XjJC D XoJC D XtJC D XyJH D X~JK D YCJJ D YHJO D YNJT D YSJJ D YXJP D Y]JP D YbJ\ D YgJ_ D YlJa D YqJk D YwJt D Y|Jm D ZAJl D ZFJf D ZKJb D ZPJf D ZUJa D ZZJ\ D Z`Jc D ZeJk D ZjJh D ZoJc D ZtJa D ZyJX D Z~JL D [DJT D [IJP D [NJZ D [SJf D [XJo D []Jz D [bKG D [gK] D [lK\ D [rK] D [wKX D [|K] D \AKX D \FKY D \KKe D \PLB D \ULt D \[MW D \`My D \eNR D \jNS D \oOF D \tOe D \yNT D \?MB D ]DLN D ]IKo D ]NK~ D ]SLT D ]XMR D ]]Na D ]bO[ D ]hOs D ]mOh D ]rO_ D ]wOy D ]|Pv D ^AQw D ^FR[ D ^KRw D ^QSd D ^VTH D ^[TN D ^`Sw D ^eT` D ^jUQ D ^oVD D ^tV} D ^zWF D ^?WU D _DXK D _IZA D _N\\ D _S]l D _X]^ D _^]c D _c_f D _hbl D _me_ D _rgr D _wkN D _|r` D `Aza D `G|f D `LyK D `Qs` D `Vm@ D `[gr D ``c| D `e`m D `j^} D `p^j D `u^N D `z\\ D `?ZC D aDXh D aIX~ D aNZj D aT[W D aYZA D a^Vu D acTC D ahSf D amTp D arUV D awUP D a}TE D bBRR D bGPd D bLOM D bQN] D bVNa D b[Ny D b`NS D bfMB D bkLG D bpKl D buKl D bzKz D b?MB D cDNe D cIOt D cOPQ D cTOy D cYNt D c^Mj D ccLk D chLO D cmLN D csKx D cxK} D c}Kn D dBKn D dGKj D dLKc D dQKP D dVKA D d\Jt D daJm D dfJ^ D dkJa D dpJc D duJX D dzJQ D d?JF D eDJB D eJJJ D eOJK D eTJP D eYJT D e^JY D ecJZ D ehJP D enJQ D esJX D exJ^ D e}J] D fBJj D fGJg D fLJh D fQJ_ D fWJO D f\JL D faJO D ffJG D fkJJ D fpJO D fuJV D fzJQ D g@JU D gEJ] D gJJ\ D gOJZ D gTJc D gYJs D g^J| D gdJ| D giJz D gnJy D gsJ} D gxKH D g}KN D hBKC D hGJz D hMJu D hRJu D hWJy D h\Ju D haJm D hfJh D hkJ\ D hpJL D hvJJ D h{JT D i@Jf D iEJs D iJKA D iOKH D iTKS D iYKS D i_KN D idKN D iiKU D inKl D isLK D ixLu D i}Mn D jCOF D jHPV D jMPd D jRPm D jWPn D j\PJ D jaO\ D jfNS D jlME D jqLT D jvKz D j{Kb D k@KC D kEJp D kJJu D kOJ~ D kUJz D kZJv D k_Jv D kdJu D kiJm D knJl D ksJc D kyJY D k~J] D lCJQ D lHJH D lMJL D lRJG D lWIy D l\Iq D lbIl D lgIk D llIl D lqIm D lvIk D l{Iy D m@Im D mEI] D mKIk D mPIm D mUIo D mZIm D m_Ih D mdIh D miIa D mnIU D msI^ D myId D m~Id D nCIa D nHIc D nMI_ D nRI] D nWIj D n]Iv D nbIv D ngIx D nlIu D nqIk D nvIp D n{Il D o@Ic D oFI^ D oKI[ D oPIT D oUIM D oZIO D o_IR D odIT D oiIP D ooIO D otIR D oyIQ D o~IM D pCIM D pHIM D pMIQ D pSIT D pXIT D p]IR D pbIO D pgIM D plIK D pqIK D pvIK D p|IG D qAIH D qFII D qKIL D qPIL D qUIO D qZII D q_II D qeIL D qjIB D qoIB D qtIF D qyH} D q~Hy D rCHx D rHHz D rNHu D rSHx D rXHy D r]Hq D rbHp D rgHq D rlHp D rrHo D rwHk D r|Ho D sAHo D sFHk D sKHj D sPHk D sUHj D s[Hg D s`Hf D seHb D sjHh SR D soHk D stHd D syHb D s~Hb D tDHf D tIHf D tNHh D tSH] D tXHa D t]Ha SR %PSKCloseWorkStation %PSKOpenWorkStation CL %%Trailer R R %DocumentFonts: Times-Bold Times-Italic Symbol Times-Roman %%Pages: 1 %%EOF ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psi_land_prow_256_250.ps�����������������������������������������������0000644�0001750�0001750�00000053662�14203121554�020756� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-1.0 %%Creator: PSIKern - An IRAF GKI Translator %%CreationDate: Tue 15:27:17 21-Aug-2007 %%DocumentFonts: (atend) %%Pages: (atend) %%BoundingBox: 2 2 557 737 %%EndComments /MAXNDC 4095 def /PageSizeXMeter 0.2594 def /PageSizeYMeter 0.1959 def /PageOffXMeter 0.001 def /PageOffYMeter 0.001 def /PortraitMode false def /PortraitRotation 0. def /LandScapeRotation 90. def /GT_LEFT 2 def /GT_RIGHT 3 def /GT_TOP 6 def /GT_BOTTOM 7 def /GT_UP 4 def /GT_DOWN 5 def /GT_CENTER 1 def /RF /Times-Roman def /GF /Symbol def /IF /Times-Italic def /BF /Times-Bold def /DASH 49 def /DOT 4 def /SPACE 24 def % How to define things. /BD { bind def } bind def /LD { load def } bind def % Constants /yes true def /no false def % Define some shorthand instructions. /GRS /grestore LD /GS /gsave LD /MK /mark LD /R /restore LD /S /save LD /SH /showpage LD /SW /setlinewidth LD % Measurement conversions. /PointperMeter 2834.64 def /MonoScale .7 def % PDF mark definition to avoid errors in printing Postscript /pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse /languagelevel where {pop languagelevel }{1} ifelse 2 lt { userdict (<<) cvn ([) cvn load put userdict (>>) cvn (]) cvn load put } if % SR: Set current path to page: - -> - /DG true store /SR { DG {1 setlinecap} {0 setlinecap} ifelse currentpoint stroke moveto /DG true store } BD % CL: CLear the page: - -> - /CL { restore showpage grestore } BD % rectfill : Fill a rectangle: x y width height -> - /rectfill where {pop} {/rectfill { gsave newpath 4 2 roll moveto exch dup 0 rlineto exch 0 exch rlineto neg 0 rlineto closepath fill grestore } BD } ifelse % SetBack: Set the background: /SetBack { /BACKCOLOR where { pop BACKCOLOR SC 0 0 MAXNDC MAXNDC rectfill } if } BD % NP: Start a new page: /NP { gsave save SetBack } BD % RC: Read Coordinates: string -> x y /RC { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } BD % DO - Determine Offset: length -> offset /DO { DS dup 0 eq { pop pop 0 } { mod } ifelse } BD % M: Move: M string -> - % Move to a point and clear length of path. /CX 0 def /CY 0 def /M { RC 2 copy /CY exch def /CX exch def moveto CT OF setdash } BD % D: Draw: D string -> - % This also calculates the length of the current path. /OF 0 def /D { RC DS 0 ne { 2 copy currentpoint exch 4 1 roll sub dup mul 3 1 roll sub dup mul add sqrt round OF add cvi DO /OF exch store } if DG { 2 copy CY eq exch CX eq and /DG exch def } if lineto } BD % Character/Font definitions. % FS -- Define the current font size: xsize ysize FS - /FontXSize 0 def /FontYSize 0 def /NewFont true def /FS { /FontYSize exch MonoScale mul store /FontXSize exch MonoScale mul store /NewFont true store } BD % PH -- Set the current path: path PH - /Path 0 def /PH { /Path exch store } BD % PA -- Set the path angle of the text: angle PA - /Angle 0 def /PA { 360 mod dup 0 lt { 360 add } if /Angle exch store /NewFont true store } BD % HJ, VJ -- Set Horizontal/Verticle Justification: just HJ/VJ - /HorzJust 0 def /VertJust 0 def /HJ { /HorzJust exch store } BD /VJ { /VertJust exch store } BD % VT -- Use mono- or variable-spaced fonts: flag VT - /VT true def /VT { /Variable exch store } BD % SetFont -- Make the required font the default: font SetFont - % Note scale factor- This is to fill out monospaced better and match other % IRAF output. /Font () def /FontMatrix matrix def /SetFont { dup Font ne NewFont or { dup /Font exch store findfont Angle matrix rotate FontXSize MonoScale div FontYSize MonoScale div matrix scale matrix concatmatrix makefont setfont Angle matrix rotate FontXSize FontYSize matrix scale matrix concatmatrix /FontMatrix exch store } { pop } ifelse /NewFont false store } BD % StringWidth -- Determine path length of string: string StringWidth - xs ys /StringWidth { Path GT_RIGHT eq Variable and { stringwidth } { Path GT_RIGHT eq { length } { pop 1 } ifelse 0 FontMatrix transform } ifelse } BD % MonoShow -- Write the string out in mono-spaced: string MonoShow - /MonoShow { /t 1 string def 1 0 FontMatrix transform /dy exch def /dx exch def Path GT_RIGHT eq { /mx dx def /my dy def } { 0 1 FontMatrix transform /my exch def /mx exch def } ifelse { t 0 3 -1 roll put t dup stringwidth dy exch sub 2 div exch dx exch sub 2 div exch gsave rmoveto show grestore mx my rmoveto } forall } BD % WS -- Draw the string: <mark> string font string ... DS - /*WSDict 13 dict def /WS { *WSDict begin counttomark 2 idiv dup dup array /FontArray exch def array /StringArray exch def 1 sub /NStrings exch def NStrings -1 0 { dup 4 1 roll exch StringArray 3 1 roll put FontArray 3 1 roll put } for pop /XSize 0 def /YSize 0 def /NChars 0 def 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get dup length NChars add /NChars exch def StringWidth YSize add /YSize exch def XSize add /XSize exch def } for /TAngle HorzJust GT_RIGHT eq { Angle 180 add dup 360 ge { 360 sub } if } { Angle } ifelse def 0 TAngle 180 le HorzJust GT_CENTER eq or { Angle sin FontYSize mul Path GT_UP eq { NChars mul } if add } if TAngle 90 ge TAngle 270 le and HorzJust GT_CENTER eq or { Path GT_RIGHT eq { XSize } { Angle cos FontXSize mul } ifelse sub } if HorzJust GT_CENTER eq { .5 mul } if /TAngle VertJust GT_TOP eq { Angle 180 sub dup 0 lt { 360 add } if } { Angle } ifelse def 0 TAngle 90 ge TAngle 270 le and VertJust GT_CENTER eq or { Angle cos FontYSize mul Path GT_UP eq { NChars mul } if sub } if TAngle 180 ge TAngle 360 le and VertJust GT_CENTER eq or { Path GT_RIGHT eq { YSize } { Angle sin FontXSize mul } ifelse sub } if VertJust GT_CENTER eq { .5 mul } if rmoveto 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get Path GT_RIGHT eq Variable and { show } { MonoShow } ifelse } for end } BD % Define the DASHED, DOTTED, DOTDASH patterns. /DS 0 def /LPS { /OF 0 store /DS 0 store 0 array } BD /LPD { /OF 0 store DASH SPACE 2 copy add /DS exch store 2 array astore } BD /LPP { /OF 0 store DOT SPACE 2 copy add /DS exch store 2 array astore } BD /LDD { /OF 0 store DOT SPACE DASH SPACE 4 copy add add add /DS exch store 4 array astore } BD % LT - Set Line Type - array -> - /CT { currentdash pop } BD /LT { /CT exch store /OF 0 store } BD % SC - Set Color: color -> - /*SCDict 1 dict def /SC { *SCDict begin /color exch def GR color get 255 div GG color get 255 div GB color get 255 div setrgbcolor end } BD % MI - Make Image LUT: table-name size MI hexstring -> - /MI { currentfile exch string readhexstring pop def } BD % Define the graphics color lookup table. /GR 16 MI FF00FF0000FF00FFFFB0FFF0D940F0F5 /GG 16 MI FF0000FF00FFFF0080D0A6E670E082DE /GB 16 MI FF000000FF00FFFF4F61008CD6D1EFB3 % Render an image when no image LUT has been define. /DefaultGrey { { currentfile inarr readhexstring pop } image } BD % ColorImage - Produce an image by indexing into the Image Lookup Table. % There are two versions of this code- one for PostScript that has % the colorimage operator and one that doesn't. If the PostScript doesn't % have colorimage, it is assumed that it can only produce black & white, % in which the Image LUT is combined to produce a single grey which is % then used as the image for the image operator. /colorimage where { pop /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length 3 mul string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def /oindex iindex 3 mul def oarr oindex IR inarr iindex get get put oarr oindex 1 add IG inarr iindex get get put oarr oindex 2 add IB inarr iindex get get put } for oarr } false 3 colorimage } BD } { /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def oarr iindex IR inarr iindex get get 0.3 mul IG inarr iindex get get 0.59 mul IB inarr iindex get get 0.11 mul add add round cvi dup 255 gt { pop 255 } if put } for oarr } image } BD } ifelse % PC: Put Cellarray: width height bitspersample matrix -> - /*PCdict 10 dict def /PC { *PCdict begin 4 -1 roll dup /inarr exch string def 4 1 roll /IR where {pop ColorImage} {DefaultGrey} ifelse end } BD % Define the Hatch (fill area) styles. H1 and H2 are handled internally % by PSIKern. Feel free to define more; there is no limit. /H3 <8888888888888888> def /H4 <ff000000ff000000> def /H5 <8844221188442211> def /H6 <1122448811224488> def % Setup for fill patterns. This whole section of code is taken almost % directly from _PostScript Language: Tutorial and Cookbook_, Adobe Systems, % 1986 (the blue book). /setuserscreendict 22 dict def setuserscreendict begin /tempctm matrix def /temprot matrix def /tempscale matrix def /concatprocs { /proc2 exch cvlit def /proc1 exch cvlit def /newproc proc1 length proc2 length add array def newproc 0 proc1 putinterval newproc proc1 length proc2 putinterval newproc cvx } def /resmatrix matrix def /findresolution { 72 0 resmatrix defaultmatrix dtransform /yres exch def /xres exch def xres dup mul yres dup mul add sqrt } def end /setuserscreen { setuserscreendict begin /spotfunction exch def /screenangle exch def /cellsize exch def /m tempctm currentmatrix def /rm screenangle temprot rotate def /sm cellsize dup tempscale scale def sm rm m m concatmatrix m concatmatrix pop 1 0 m dtransform /y1 exch def /x1 exch def /veclength x1 dup mul y1 dup mul add sqrt def /frequency findresolution veclength div def /newscreenangle y1 x1 atan def m 2 get m 1 get mul m 0 get m 3 get mul sub 0 gt { {neg} /spotfunction load concatprocs /spotfunction exch def } if frequency newscreenangle /spotfunction load setscreen end } def /setpatterndict 18 dict def setpatterndict begin /bitison { /ybit exch def /xbit exch def /bytevalue bstring ybit bwidth mul xbit 8 idiv add get def /mask 1 7 xbit 8 mod sub bitshift def bytevalue mask and 0 ne } def end /bitpatternspotfunction { setpatterndict begin /y exch def /x exch def /xindex x 1 add 2 div bpside mul cvi def /yindex y 1 add 2 div bpside mul cvi def xindex yindex bitison { /onbits onbits 1 add def 1 } { /offbits offbits 1 add def 0 } ifelse end } def /setpattern { setpatterndict begin /cellsz exch def /angle exch def /bwidth exch def /bpside exch def /bstring exch def /onbits 0 def /offbits 0 def cellsz angle /bitpatternspotfunction load setuserscreen { } settransfer offbits offbits onbits add div setgray end } def % SP: Set Pattern: pattern -> - /SP { 8 1 0 MAXNDC 100 div setpattern } BD % FI: Fill with Pattern: - -> - /FI { fill grestore } BD % Set the transformation matrix. /SS { PointperMeter dup scale PageOffXMeter PortraitMode not { PageSizeYMeter add } if PageOffYMeter translate PortraitMode { PortraitRotation rotate } { LandScapeRotation rotate} ifelse PageSizeXMeter MAXNDC div PageSizeYMeter MAXNDC div scale 1 setlinejoin 0 setlinecap } BD % Save the current state of VM S /GR 16 MI FF00FF000000FFFFA02DFFEFD83FEDF4 /GG 16 MI FF0000FF00FFFF00214FA5E570E082DD /GB 16 MI FF000000FFFF00FFEF4F008CD6D1EDB2 SS S %%EndProlog %PSKOpenWorkStation %%Page: 1 1 NP 9 SC GS M @@@@ D ??@@ D ???? D @@?? FI 0 SC GS M KaH] D t]H] D t]|f D Ka|f FI LPS LT 7 SW 5 SC M KaH] D MBH] D MBIH D MBH] D NiH] D NiIH D NiH] D POH] D POIH D POH] D QvH] D QvIH D QvH] D S\H] D S\Is D S\H] SR 0 PA 81.85751 108.2269 FS 3 PH 1 HJ 6 VJ yes VT 6 SC M S\Gg MK BF (100) WS 5 SC M S\H] D UCH] D UCIH D UCH] D ViH] D ViIH D ViH] D XPH] D XPIH D XPH] D YwH] D YwIH D YwH] D []H] D []Is D []H] SR 6 SC M []Gg MK BF (200) WS 5 SC M []H] D ]DH] D ]DIH D ]DH] D ^jH] D ^jIH D ^jH] D `QH] D `QIH D `QH] D awH] D awIH D awH] D c^H] D c^Is D c^H] SR 6 SC M c^Gg MK BF (300) WS 5 SC M c^H] D eDH] D eDIH D eDH] D fkH] D fkIH D fkH] D hRH] D hRIH D hRH] D ixH] D ixIH D ixH] D k_H] D k_Is D k_H] SR 6 SC M k_Gg MK BF (400) WS 5 SC M k_H] D mEH] D mEIH D mEH] D nlH] D nlIH D nlH] D pSH] D pSIH D pSH] D qyH] D qyIH D qyH] D s`H] D s`Is D s`H] SR 6 SC M s`Gg MK BF (500) WS 5 SC M s`H] D t]H] SR M t]H] D t]If D s}If D t]If D t]Kf D s}Kf D t]Kf D t]Mf D s}Mf D t]Mf D t]Of D s}Of D t]Of D t]Qf D s\Qf D t]Qf D t]Sf D s}Sf D t]Sf D t]Ug D s}Ug D t]Ug D t]Wg D s}Wg D t]Wg D t]Yg D s}Yg D t]Yg D t][g D s\[g D t][g D t]]g D s}]g D t]]g D t]_g D s}_g D t]_g D t]ah D s}ah D t]ah D t]ch D s}ch D t]ch D t]eh D s\eh D t]eh D t]gh D s}gh D t]gh D t]ih D s}ih D t]ih D t]kh D s}kh D t]kh D t]mi D s}mi D t]mi D t]oi D s\oi D t]oi D t]qi D s}qi D t]qi D t]si D s}si D t]si D t]ui D s}ui D t]ui D t]wi D s}wi D t]wi D t]yi D s\yi D t]yi D t]{j D s}{j D t]{j D t]|f SR M KaH] D KaIf D LAIf D KaIf D KaKf D LAKf D KaKf D KaMf D LAMf D KaMf D KaOf D LAOf D KaOf D KaQf D LaQf D KaQf SR 3 HJ 1 VJ 6 SC M JxQf MK BF (500) WS 5 SC M KaQf D KaSf D LASf D KaSf D KaUg D LAUg D KaUg D KaWg D LAWg D KaWg D KaYg D LAYg D KaYg D Ka[g D La[g D Ka[g SR 6 SC M Jx[g MK BF (1000) WS 5 SC M Ka[g D Ka]g D LA]g D Ka]g D Ka_g D LA_g D Ka_g D Kaah D LAah D Kaah D Kach D LAch D Kach D Kaeh D Laeh D Kaeh SR 6 SC M Jxeh MK BF (1500) WS 5 SC M Kaeh D Kagh D LAgh D Kagh D Kaih D LAih D Kaih D Kakh D LAkh D Kakh D Kami D LAmi D Kami D Kaoi D Laoi D Kaoi SR 6 SC M Jxoi MK BF (2000) WS 5 SC M Kaoi D Kaqi D LAqi D Kaqi D Kasi D LAsi D Kasi D Kaui D LAui D Kaui D Kawi D LAwi D Kawi D Kayi D Layi D Kayi SR 6 SC M Jxyj MK BF (2500) WS 5 SC M Kayi D Ka{j D LA{j D Ka{j D Ka|f SR M Ka|f D MB|f D MB{{ D MB|f D Ni|f D Ni{{ D Ni|f D PO|f D PO{{ D PO|f D Qv|f D Qv{{ D Qv|f D S\|f D S\{P D S\|f D UC|f D UC{{ D UC|f D Vi|f D Vi{{ D Vi|f D XP|f D XP{{ D XP|f D Yw|f D Yw{{ D Yw|f D []|f D []{P D []|f D ]D|f D ]D{{ D ]D|f D ^j|f D ^j{{ D ^j|f D `Q|f D `Q{{ D `Q|f D aw|f D aw{{ D aw|f D c^|f D c^{P D c^|f D eD|f D eD{{ D eD|f D fk|f D fk{{ D fk|f D hR|f D hR{{ D hR|f D ix|f D ix{{ D ix|f D k_|f D k_{P D k_|f D mE|f D mE{{ D mE|f D nl|f D nl{{ D nl|f D pS|f D pS{{ D pS|f D qy|f D qy{{ D qy|f D s`|f D s`{P D s`|f D t]|f SR 1 HJ 3 SC M _?DN MK BF (Column \(pixels\)) WS 7 VJ M _?}\ MK BF (dev$pix: row 256) WS 3 SW 1 SC M KaHc D KfHd D KkHd D KpHd D KuHc D KzHf D K?Ha D LDHh D LJHf D LOHa D LTHf D LYHd D L^Hb D LcHh D LhHf D LmHg D LsHh D LxHo D L}Hk D MBHo D MGHt D MLHm D MQHp D MVHm D M\Ht D MaHo D MfHq D MkHt D MpHu D MuHq D MzHu D N@Hs D NEHq D NJHu D NOHx D NTHt D NYHx D N^Hz D NcHu D NiHt D NnHv D NsHy D NxHt D N}Hu D OBH} D OGIC D OLIB D ORIF D OWID D O\IA D OaID D OfII D OkIF D OpIG D OvIQ D O{IO D P@IR D PEIY D PJIY D POIb D PTIj D PYIl D P_Iq D PdI} D PiIv D PnIo D PsIh D PxId D P}Id D QBIl D QHIx D QMJG D QRJB D QWI} D Q\Iz D QaJA D QfI} D QkIs D QqIk D QvIo D Q{Io D R@Io D REIh D RJIj D ROIj D RTIm D RZIj D R_Id D RdId D RiI_ D RnIY D RsIZ D RxI[ D R}IU D SCI^ D SHI_ D SMIc D SRI^ D SWId D S\Ia D SaIa D SfIl D SlIx D SqIt D SvI~ D S{Iu D T@I{ D TEI{ D TJIy D TPIt D TUIv D TZIt D T_Is D TdIp D TiIu D TnI~ D TsI? D TyI? D T~I~ D UCJJ D UHJJ D UMJS D URJP D UWJJ D U\JK D UbJC D UgJG D UlJD D UqJB D UvJM D U{JX D V@Jd D VEJt D VKJ? D VPKT D VUKb D VZKb D V_Ke D VdKo D ViKy D VoKz D VtLB D VyLG D V~LT D WCLl D WHLx D WMLj D WRLT D WXKy D W]Kb D WbKP D WgKC D WlJ~ D WqJs D WvJf D W{J\ D XAJZ D XFJ\ D XKJX D XPJY D XUJU D XZJP D X_JS D XeJP D XjJC D XoJC D XtJC D XyJH D X~JK D YCJJ D YHJO D YNJT D YSJJ D YXJP D Y]JP D YbJ\ D YgJ_ D YlJa D YqJk D YwJt D Y|Jm D ZAJl D ZFJf D ZKJb D ZPJf D ZUJa D ZZJ\ D Z`Jc D ZeJk D ZjJh D ZoJc D ZtJa D ZyJX D Z~JL D [DJT D [IJP D [NJZ D [SJf D [XJo D []Jz D [bKG D [gK] D [lK\ D [rK] D [wKX D [|K] D \AKX D \FKY D \KKe D \PLB D \ULt D \[MW D \`My D \eNR D \jNS D \oOF D \tOe D \yNT D \?MB D ]DLN D ]IKo D ]NK~ D ]SLT D ]XMR D ]]Na D ]bO[ D ]hOs D ]mOh D ]rO_ D ]wOy D ]|Pv D ^AQw D ^FR[ D ^KRw D ^QSd D ^VTH D ^[TN D ^`Sw D ^eT` D ^jUQ D ^oVD D ^tV} D ^zWF D ^?WU D _DXK D _IZA D _N\\ D _S]l D _X]^ D _^]c D _c_f D _hbl D _me_ D _rgr D _wkN D _|r` D `Aza D `G|f D `LyK D `Qs` D `Vm@ D `[gr D ``c| D `e`m D `j^} D `p^j D `u^N D `z\\ D `?ZC D aDXh D aIX~ D aNZj D aT[W D aYZA D a^Vu D acTC D ahSf D amTp D arUV D awUP D a}TE D bBRR D bGPd D bLOM D bQN] D bVNa D b[Ny D b`NS D bfMB D bkLG D bpKl D buKl D bzKz D b?MB D cDNe D cIOt D cOPQ D cTOy D cYNt D c^Mj D ccLk D chLO D cmLN D csKx D cxK} D c}Kn D dBKn D dGKj D dLKc D dQKP D dVKA D d\Jt D daJm D dfJ^ D dkJa D dpJc D duJX D dzJQ D d?JF D eDJB D eJJJ D eOJK D eTJP D eYJT D e^JY D ecJZ D ehJP D enJQ D esJX D exJ^ D e}J] D fBJj D fGJg D fLJh D fQJ_ D fWJO D f\JL D faJO D ffJG D fkJJ D fpJO D fuJV D fzJQ D g@JU D gEJ] D gJJ\ D gOJZ D gTJc D gYJs D g^J| D gdJ| D giJz D gnJy D gsJ} D gxKH D g}KN D hBKC D hGJz D hMJu D hRJu D hWJy D h\Ju D haJm D hfJh D hkJ\ D hpJL D hvJJ D h{JT D i@Jf D iEJs D iJKA D iOKH D iTKS D iYKS D i_KN D idKN D iiKU D inKl D isLK D ixLu D i}Mn D jCOF D jHPV D jMPd D jRPm D jWPn D j\PJ D jaO\ D jfNS D jlME D jqLT D jvKz D j{Kb D k@KC D kEJp D kJJu D kOJ~ D kUJz D kZJv D k_Jv D kdJu D kiJm D knJl D ksJc D kyJY D k~J] D lCJQ D lHJH D lMJL D lRJG D lWIy D l\Iq D lbIl D lgIk D llIl D lqIm D lvIk D l{Iy D m@Im D mEI] D mKIk D mPIm D mUIo D mZIm D m_Ih D mdIh D miIa D mnIU D msI^ D myId D m~Id D nCIa D nHIc D nMI_ D nRI] D nWIj D n]Iv D nbIv D ngIx D nlIu D nqIk D nvIp D n{Il D o@Ic D oFI^ D oKI[ D oPIT D oUIM D oZIO D o_IR D odIT D oiIP D ooIO D otIR D oyIQ D o~IM D pCIM D pHIM D pMIQ D pSIT D pXIT D p]IR D pbIO D pgIM D plIK D pqIK D pvIK D p|IG D qAIH D qFII D qKIL D qPIL D qUIO D qZII D q_II D qeIL D qjIB D qoIB D qtIF D qyH} D q~Hy D rCHx D rHHz D rNHu D rSHx D rXHy D r]Hq D rbHp D rgHq D rlHp D rrHo D rwHk D r|Ho D sAHo D sFHk D sKHj D sPHk D sUHj D s[Hg D s`Hf D seHb D sjHh SR D soHk D stHd D syHb D s~Hb D tDHf D tIHf D tNHh D tSH] D tXHa D t]Ha SR %PSKCloseWorkStation %PSKOpenWorkStation M KaH_ D KfH^ D KkHc D KpHd D KuH_ D KzHc D K?H] D LDHa D LJHd D LOHa D LTHd D LYHa D L^Hg D LcHc D LhHk D LmHf D LsHg D LxHk D L}Hk D MBHo D MGHo D MLHj D MQHp D MVHt D M\Hq D MaHu D MfHv D MkHs D MpHu D MuHy D MzHv D N@Ht D NEHv D NJHv D NOHc D NQH] SR M NUH] D NYI] D N^I{ D NcIK D NiHu D NnHu D NsHz D NxHv D N}Hx D OBHv D OGHv D OLH? D ORH~ D OWH~ D O\IF D OaIC D OfIF D OkIU D OpI_ D OvIg D O{Ia D P@IZ D PEI[ D PJI^ D POIg D PTIs D PYJD D P_JM D PdJB D PiIs D PnIm D PsJB D PxJb D P}Jt D QBJf D QHJY D QMJM D QRJP D QWJX D Q\J\ D QaJ] D QfJY D QkJ^ D QqJV D QvJP D Q{JD D R@J] D REJg D RJJk D ROJP D RTIt D RZId D R_Ia D RdIX D RiIQ D RnIO D RsIM D RxIV D R}I] D SCI^ D SHIc D SMId D SRI[ D SWI^ D S\Id D SaIj D SfIo D SlIq D SqIm D SvIo D S{Iq D T@I~ D TEJH D TJI~ D TPI~ D TUI{ D TZI? D T_JA D TdI} D TiI{ D TnIu D TsIt D TyIt D T~Ip D UCIp D UHIs D UMIv D URI~ D UWJC D U\JJ D UbJP D UgJ\ D UlJb D UqJc D UvJc D U{Jd D V@Jh D VEJs D VKJ| D VPKP D VUKe D VZKw D V_LQ D VdLg D ViLe D VoLa D VtL\ D VyLa D V~L} D WCN@ D WHOA D WMNm D WRMc D WXLk D W]Lb D WbML D WgMj D WlM[ D WqLx D WvLU D W{Kt D XAKY D XFKH D XKJy D XPJl D XUJb D XZJS D X_JJ D XeJG D XjJA D XoJC D XtJS D XyJ\ D X~JS D YCJK D YHJH D YNJH D YSJJ D YXJY D Y]Ja D YbJd D YgJk D YlJf D YqJb D YwJ\ D Y|Jb D ZAJp D ZFJ} D ZKJ} D ZPJq D ZUJf D ZZJb D Z`JQ D ZeJU D ZjJM D ZoJT D ZtJU D ZyJO D Z~JT D [DJH D [IJH D [NJJ D [SJX D [XJd D []Jz D [bJ? D [gKJ D [lKJ D [rK] D [wLH D [|LU D \ALc D \FLi D \KLt D \PLc D \UL` D \[L} D \`MR D \eMb D \jNS D \oOD D \tNa D \yNO D \?Mu D ]DMC D ]ILl D ]NLT D ]SLA D ]XLF D ]]LU D ]bLS D ]hLY D ]mLZ D ]rLk D ]wMa D ]|OI D ^AQO D ^FSJ D ^KTA D ^QSq D ^VSq D ^[Sv D ^`So D ^eSx D ^jTG D ^oTd D ^tT_ D ^zTc D ^?Ti D _DU~ D _IX@ D _NXy D _SXu D _XXP D _^X@ D _cWu D _hWr D _mX[ D _rYp D _w[E D _|[t D `A\A D `G[z D `L[| D `Q\B D `V\L D `[\g D ``\g D `e[X D `jYZ D `pWr D `uV` D `zTw D `?SR D aDS^ D aITQ D aNSc D aTRQ D aYRA D a^RA D acP? D ahPB D amO` D arOi D awQ@ D a}RV D bBRw D bGRo D bLQn D bQP@ D bVN? D b[Nk D b`Mo D bfLg D bkKl D bpKc D buKb D bzKk D b?Kx D cDLY D cILs D cOLo D cTL] D cYK| D c^LE D ccLH D chLP D cmLk D csLa D cxLA D c}Kl D dBKb D dGKJ D dLKB D dQKB D dVJv D d\Jp D daJY D dfJS D dkJV D dpJU D duJJ D dzJT D d?JZ D eDJQ D eJJJ D eOJK D eTJH D eYJB D e^JD D ecJL D ehJJ D enI~ D esI~ D exI? D e}JF D fBJQ D fGJ\ D fLJc D fQJh D fWJm D f\J_ D faJZ D ffJ^ D fkJU D fpJU D fuJK D fzJF D g@JF D gEJK D gJJJ D gOJG D gTJT D gYJb D g^Jj D gdJu D giJu D gnJu D gsJ? D gxKA D g}J? D hBJ~ D hGKA D hMKG D hRKJ D hWKN D h\KO D haKO D hfKH D hkJ| D hpJv D hvJu D h{Jl D i@Jb D iEJl D iJJu D iOJ? D iTKN D iYKQ D i_KX D idKY D iiKV D inKk D isLQ D ixM@ D i}MP D jCMF D jHLu D jMLi D jRLo D jWM[ D j\Mn D jaM[ D jfMA D jlLo D jqLX D jvLA D j{Kk D k@KZ D kEKY D kJK_ D kOKk D kUKx D kZKq D k_K_ D kdKH D kiJp D knJt D ksKN D kyKh D k~Kh D lCKN D lHKA D lMJ} D lRJk D lWJ^ D l\JV D lbJO D lgJF D llJB D lqIx D lvIq D l{Ip D m@Io D mEIl D mKIo D mPIh D mUIc D mZIt D m_Ix D mdIx D miI~ D mnJB D msJL D myJB D m~Iv D nCIv D nHIo D nMIc D nRIZ D nWIa D n]If D nbIk D ngIl D nlIu D nqI{ D nvJH D n{JZ D o@JU D oFJG D oKJF D oPI{ D oUIt D oZIp D o_Io D odIs D oiIl D ooIj D otI_ D oyIO D o~IF D pCIC D pHIP D pMIZ D pSIa D pXIf D p]Ij D pbIc D pgIX D plIY D pqIX D pvIO D p|IK D qAIH D qFID D qKIG D qPIF D qUIF D qZIB D q_H? D qeID D qjIF D qoIB D qtH? D qyIB D q~H~ D rCH} D rHHz D rNH| D rSH} D rXHv D r]Hv D rbHs D rgHv D rlHq D rrHo D rwHo D r|Ho D sAHm D sFHk D sKHf D sPHh D sUHh D s[Hh D s`Hg D seHk D sjHj D soHh D stHj D syHj D s~Hb D tDHg D tIHc D tNHa D tSHf D tXH_ D t]Ha SR %PSKCloseWorkStation %PSKOpenWorkStation CL %%Trailer R R %DocumentFonts: Times-Bold Times-Italic Symbol Times-Roman %%Pages: 1 %%EOF ������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/data/psi_land_prow_256_250_200.ps�������������������������������������������0000644�0001750�0001750�00000063027�14203121554�021333� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%!PS-Adobe-1.0 %%Creator: PSIKern - An IRAF GKI Translator %%CreationDate: Tue 15:31:03 21-Aug-2007 %%DocumentFonts: (atend) %%Pages: (atend) %%BoundingBox: 2 2 557 737 %%EndComments /MAXNDC 4095 def /PageSizeXMeter 0.2594 def /PageSizeYMeter 0.1959 def /PageOffXMeter 0.001 def /PageOffYMeter 0.001 def /PortraitMode false def /PortraitRotation 0. def /LandScapeRotation 90. def /GT_LEFT 2 def /GT_RIGHT 3 def /GT_TOP 6 def /GT_BOTTOM 7 def /GT_UP 4 def /GT_DOWN 5 def /GT_CENTER 1 def /RF /Times-Roman def /GF /Symbol def /IF /Times-Italic def /BF /Times-Bold def /DASH 49 def /DOT 4 def /SPACE 24 def % How to define things. /BD { bind def } bind def /LD { load def } bind def % Constants /yes true def /no false def % Define some shorthand instructions. /GRS /grestore LD /GS /gsave LD /MK /mark LD /R /restore LD /S /save LD /SH /showpage LD /SW /setlinewidth LD % Measurement conversions. /PointperMeter 2834.64 def /MonoScale .7 def % PDF mark definition to avoid errors in printing Postscript /pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse /languagelevel where {pop languagelevel }{1} ifelse 2 lt { userdict (<<) cvn ([) cvn load put userdict (>>) cvn (]) cvn load put } if % SR: Set current path to page: - -> - /DG true store /SR { DG {1 setlinecap} {0 setlinecap} ifelse currentpoint stroke moveto /DG true store } BD % CL: CLear the page: - -> - /CL { restore showpage grestore } BD % rectfill : Fill a rectangle: x y width height -> - /rectfill where {pop} {/rectfill { gsave newpath 4 2 roll moveto exch dup 0 rlineto exch 0 exch rlineto neg 0 rlineto closepath fill grestore } BD } ifelse % SetBack: Set the background: /SetBack { /BACKCOLOR where { pop BACKCOLOR SC 0 0 MAXNDC MAXNDC rectfill } if } BD % NP: Start a new page: /NP { gsave save SetBack } BD % RC: Read Coordinates: string -> x y /RC { currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or currentfile read pop 8#77 and 6 bitshift currentfile read pop 8#77 and or } BD % DO - Determine Offset: length -> offset /DO { DS dup 0 eq { pop pop 0 } { mod } ifelse } BD % M: Move: M string -> - % Move to a point and clear length of path. /CX 0 def /CY 0 def /M { RC 2 copy /CY exch def /CX exch def moveto CT OF setdash } BD % D: Draw: D string -> - % This also calculates the length of the current path. /OF 0 def /D { RC DS 0 ne { 2 copy currentpoint exch 4 1 roll sub dup mul 3 1 roll sub dup mul add sqrt round OF add cvi DO /OF exch store } if DG { 2 copy CY eq exch CX eq and /DG exch def } if lineto } BD % Character/Font definitions. % FS -- Define the current font size: xsize ysize FS - /FontXSize 0 def /FontYSize 0 def /NewFont true def /FS { /FontYSize exch MonoScale mul store /FontXSize exch MonoScale mul store /NewFont true store } BD % PH -- Set the current path: path PH - /Path 0 def /PH { /Path exch store } BD % PA -- Set the path angle of the text: angle PA - /Angle 0 def /PA { 360 mod dup 0 lt { 360 add } if /Angle exch store /NewFont true store } BD % HJ, VJ -- Set Horizontal/Verticle Justification: just HJ/VJ - /HorzJust 0 def /VertJust 0 def /HJ { /HorzJust exch store } BD /VJ { /VertJust exch store } BD % VT -- Use mono- or variable-spaced fonts: flag VT - /VT true def /VT { /Variable exch store } BD % SetFont -- Make the required font the default: font SetFont - % Note scale factor- This is to fill out monospaced better and match other % IRAF output. /Font () def /FontMatrix matrix def /SetFont { dup Font ne NewFont or { dup /Font exch store findfont Angle matrix rotate FontXSize MonoScale div FontYSize MonoScale div matrix scale matrix concatmatrix makefont setfont Angle matrix rotate FontXSize FontYSize matrix scale matrix concatmatrix /FontMatrix exch store } { pop } ifelse /NewFont false store } BD % StringWidth -- Determine path length of string: string StringWidth - xs ys /StringWidth { Path GT_RIGHT eq Variable and { stringwidth } { Path GT_RIGHT eq { length } { pop 1 } ifelse 0 FontMatrix transform } ifelse } BD % MonoShow -- Write the string out in mono-spaced: string MonoShow - /MonoShow { /t 1 string def 1 0 FontMatrix transform /dy exch def /dx exch def Path GT_RIGHT eq { /mx dx def /my dy def } { 0 1 FontMatrix transform /my exch def /mx exch def } ifelse { t 0 3 -1 roll put t dup stringwidth dy exch sub 2 div exch dx exch sub 2 div exch gsave rmoveto show grestore mx my rmoveto } forall } BD % WS -- Draw the string: <mark> string font string ... DS - /*WSDict 13 dict def /WS { *WSDict begin counttomark 2 idiv dup dup array /FontArray exch def array /StringArray exch def 1 sub /NStrings exch def NStrings -1 0 { dup 4 1 roll exch StringArray 3 1 roll put FontArray 3 1 roll put } for pop /XSize 0 def /YSize 0 def /NChars 0 def 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get dup length NChars add /NChars exch def StringWidth YSize add /YSize exch def XSize add /XSize exch def } for /TAngle HorzJust GT_RIGHT eq { Angle 180 add dup 360 ge { 360 sub } if } { Angle } ifelse def 0 TAngle 180 le HorzJust GT_CENTER eq or { Angle sin FontYSize mul Path GT_UP eq { NChars mul } if add } if TAngle 90 ge TAngle 270 le and HorzJust GT_CENTER eq or { Path GT_RIGHT eq { XSize } { Angle cos FontXSize mul } ifelse sub } if HorzJust GT_CENTER eq { .5 mul } if /TAngle VertJust GT_TOP eq { Angle 180 sub dup 0 lt { 360 add } if } { Angle } ifelse def 0 TAngle 90 ge TAngle 270 le and VertJust GT_CENTER eq or { Angle cos FontYSize mul Path GT_UP eq { NChars mul } if sub } if TAngle 180 ge TAngle 360 le and VertJust GT_CENTER eq or { Path GT_RIGHT eq { YSize } { Angle sin FontXSize mul } ifelse sub } if VertJust GT_CENTER eq { .5 mul } if rmoveto 0 1 NStrings { dup FontArray exch get SetFont StringArray exch get Path GT_RIGHT eq Variable and { show } { MonoShow } ifelse } for end } BD % Define the DASHED, DOTTED, DOTDASH patterns. /DS 0 def /LPS { /OF 0 store /DS 0 store 0 array } BD /LPD { /OF 0 store DASH SPACE 2 copy add /DS exch store 2 array astore } BD /LPP { /OF 0 store DOT SPACE 2 copy add /DS exch store 2 array astore } BD /LDD { /OF 0 store DOT SPACE DASH SPACE 4 copy add add add /DS exch store 4 array astore } BD % LT - Set Line Type - array -> - /CT { currentdash pop } BD /LT { /CT exch store /OF 0 store } BD % SC - Set Color: color -> - /*SCDict 1 dict def /SC { *SCDict begin /color exch def GR color get 255 div GG color get 255 div GB color get 255 div setrgbcolor end } BD % MI - Make Image LUT: table-name size MI hexstring -> - /MI { currentfile exch string readhexstring pop def } BD % Define the graphics color lookup table. /GR 16 MI FF00FF0000FF00FFFFB0FFF0D940F0F5 /GG 16 MI FF0000FF00FFFF0080D0A6E670E082DE /GB 16 MI FF000000FF00FFFF4F61008CD6D1EFB3 % Render an image when no image LUT has been define. /DefaultGrey { { currentfile inarr readhexstring pop } image } BD % ColorImage - Produce an image by indexing into the Image Lookup Table. % There are two versions of this code- one for PostScript that has % the colorimage operator and one that doesn't. If the PostScript doesn't % have colorimage, it is assumed that it can only produce black & white, % in which the Image LUT is combined to produce a single grey which is % then used as the image for the image operator. /colorimage where { pop /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length 3 mul string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def /oindex iindex 3 mul def oarr oindex IR inarr iindex get get put oarr oindex 1 add IG inarr iindex get get put oarr oindex 2 add IB inarr iindex get get put } for oarr } false 3 colorimage } BD } { /ColorImage { { currentfile inarr readhexstring pop dup /inarr exch def length string /oarr exch def 0 1 inarr length 1 sub { /iindex exch def oarr iindex IR inarr iindex get get 0.3 mul IG inarr iindex get get 0.59 mul IB inarr iindex get get 0.11 mul add add round cvi dup 255 gt { pop 255 } if put } for oarr } image } BD } ifelse % PC: Put Cellarray: width height bitspersample matrix -> - /*PCdict 10 dict def /PC { *PCdict begin 4 -1 roll dup /inarr exch string def 4 1 roll /IR where {pop ColorImage} {DefaultGrey} ifelse end } BD % Define the Hatch (fill area) styles. H1 and H2 are handled internally % by PSIKern. Feel free to define more; there is no limit. /H3 <8888888888888888> def /H4 <ff000000ff000000> def /H5 <8844221188442211> def /H6 <1122448811224488> def % Setup for fill patterns. This whole section of code is taken almost % directly from _PostScript Language: Tutorial and Cookbook_, Adobe Systems, % 1986 (the blue book). /setuserscreendict 22 dict def setuserscreendict begin /tempctm matrix def /temprot matrix def /tempscale matrix def /concatprocs { /proc2 exch cvlit def /proc1 exch cvlit def /newproc proc1 length proc2 length add array def newproc 0 proc1 putinterval newproc proc1 length proc2 putinterval newproc cvx } def /resmatrix matrix def /findresolution { 72 0 resmatrix defaultmatrix dtransform /yres exch def /xres exch def xres dup mul yres dup mul add sqrt } def end /setuserscreen { setuserscreendict begin /spotfunction exch def /screenangle exch def /cellsize exch def /m tempctm currentmatrix def /rm screenangle temprot rotate def /sm cellsize dup tempscale scale def sm rm m m concatmatrix m concatmatrix pop 1 0 m dtransform /y1 exch def /x1 exch def /veclength x1 dup mul y1 dup mul add sqrt def /frequency findresolution veclength div def /newscreenangle y1 x1 atan def m 2 get m 1 get mul m 0 get m 3 get mul sub 0 gt { {neg} /spotfunction load concatprocs /spotfunction exch def } if frequency newscreenangle /spotfunction load setscreen end } def /setpatterndict 18 dict def setpatterndict begin /bitison { /ybit exch def /xbit exch def /bytevalue bstring ybit bwidth mul xbit 8 idiv add get def /mask 1 7 xbit 8 mod sub bitshift def bytevalue mask and 0 ne } def end /bitpatternspotfunction { setpatterndict begin /y exch def /x exch def /xindex x 1 add 2 div bpside mul cvi def /yindex y 1 add 2 div bpside mul cvi def xindex yindex bitison { /onbits onbits 1 add def 1 } { /offbits offbits 1 add def 0 } ifelse end } def /setpattern { setpatterndict begin /cellsz exch def /angle exch def /bwidth exch def /bpside exch def /bstring exch def /onbits 0 def /offbits 0 def cellsz angle /bitpatternspotfunction load setuserscreen { } settransfer offbits offbits onbits add div setgray end } def % SP: Set Pattern: pattern -> - /SP { 8 1 0 MAXNDC 100 div setpattern } BD % FI: Fill with Pattern: - -> - /FI { fill grestore } BD % Set the transformation matrix. /SS { PointperMeter dup scale PageOffXMeter PortraitMode not { PageSizeYMeter add } if PageOffYMeter translate PortraitMode { PortraitRotation rotate } { LandScapeRotation rotate} ifelse PageSizeXMeter MAXNDC div PageSizeYMeter MAXNDC div scale 1 setlinejoin 0 setlinecap } BD % Save the current state of VM S /GR 16 MI FF00FF000000FFFFA02DFFEFD83FEDF4 /GG 16 MI FF0000FF00FFFF00214FA5E570E082DD /GB 16 MI FF000000FFFF00FFEF4F008CD6D1EDB2 SS S %%EndProlog %PSKOpenWorkStation %%Page: 1 1 NP 9 SC GS M @@@@ D ??@@ D ???? D @@?? FI 0 SC GS M KaH] D t]H] D t]|f D Ka|f FI LPS LT 7 SW 5 SC M KaH] D MBH] D MBIH D MBH] D NiH] D NiIH D NiH] D POH] D POIH D POH] D QvH] D QvIH D QvH] D S\H] D S\Is D S\H] SR 0 PA 81.85751 108.2269 FS 3 PH 1 HJ 6 VJ yes VT 6 SC M S\Gg MK BF (100) WS 5 SC M S\H] D UCH] D UCIH D UCH] D ViH] D ViIH D ViH] D XPH] D XPIH D XPH] D YwH] D YwIH D YwH] D []H] D []Is D []H] SR 6 SC M []Gg MK BF (200) WS 5 SC M []H] D ]DH] D ]DIH D ]DH] D ^jH] D ^jIH D ^jH] D `QH] D `QIH D `QH] D awH] D awIH D awH] D c^H] D c^Is D c^H] SR 6 SC M c^Gg MK BF (300) WS 5 SC M c^H] D eDH] D eDIH D eDH] D fkH] D fkIH D fkH] D hRH] D hRIH D hRH] D ixH] D ixIH D ixH] D k_H] D k_Is D k_H] SR 6 SC M k_Gg MK BF (400) WS 5 SC M k_H] D mEH] D mEIH D mEH] D nlH] D nlIH D nlH] D pSH] D pSIH D pSH] D qyH] D qyIH D qyH] D s`H] D s`Is D s`H] SR 6 SC M s`Gg MK BF (500) WS 5 SC M s`H] D t]H] SR M t]H] D t]If D s}If D t]If D t]Kf D s}Kf D t]Kf D t]Mf D s}Mf D t]Mf D t]Of D s}Of D t]Of D t]Qf D s\Qf D t]Qf D t]Sf D s}Sf D t]Sf D t]Ug D s}Ug D t]Ug D t]Wg D s}Wg D t]Wg D t]Yg D s}Yg D t]Yg D t][g D s\[g D t][g D t]]g D s}]g D t]]g D t]_g D s}_g D t]_g D t]ah D s}ah D t]ah D t]ch D s}ch D t]ch D t]eh D s\eh D t]eh D t]gh D s}gh D t]gh D t]ih D s}ih D t]ih D t]kh D s}kh D t]kh D t]mi D s}mi D t]mi D t]oi D s\oi D t]oi D t]qi D s}qi D t]qi D t]si D s}si D t]si D t]ui D s}ui D t]ui D t]wi D s}wi D t]wi D t]yi D s\yi D t]yi D t]{j D s}{j D t]{j D t]|f SR M KaH] D KaIf D LAIf D KaIf D KaKf D LAKf D KaKf D KaMf D LAMf D KaMf D KaOf D LAOf D KaOf D KaQf D LaQf D KaQf SR 3 HJ 1 VJ 6 SC M JxQf MK BF (500) WS 5 SC M KaQf D KaSf D LASf D KaSf D KaUg D LAUg D KaUg D KaWg D LAWg D KaWg D KaYg D LAYg D KaYg D Ka[g D La[g D Ka[g SR 6 SC M Jx[g MK BF (1000) WS 5 SC M Ka[g D Ka]g D LA]g D Ka]g D Ka_g D LA_g D Ka_g D Kaah D LAah D Kaah D Kach D LAch D Kach D Kaeh D Laeh D Kaeh SR 6 SC M Jxeh MK BF (1500) WS 5 SC M Kaeh D Kagh D LAgh D Kagh D Kaih D LAih D Kaih D Kakh D LAkh D Kakh D Kami D LAmi D Kami D Kaoi D Laoi D Kaoi SR 6 SC M Jxoi MK BF (2000) WS 5 SC M Kaoi D Kaqi D LAqi D Kaqi D Kasi D LAsi D Kasi D Kaui D LAui D Kaui D Kawi D LAwi D Kawi D Kayi D Layi D Kayi SR 6 SC M Jxyj MK BF (2500) WS 5 SC M Kayi D Ka{j D LA{j D Ka{j D Ka|f SR M Ka|f D MB|f D MB{{ D MB|f D Ni|f D Ni{{ D Ni|f D PO|f D PO{{ D PO|f D Qv|f D Qv{{ D Qv|f D S\|f D S\{P D S\|f D UC|f D UC{{ D UC|f D Vi|f D Vi{{ D Vi|f D XP|f D XP{{ D XP|f D Yw|f D Yw{{ D Yw|f D []|f D []{P D []|f D ]D|f D ]D{{ D ]D|f D ^j|f D ^j{{ D ^j|f D `Q|f D `Q{{ D `Q|f D aw|f D aw{{ D aw|f D c^|f D c^{P D c^|f D eD|f D eD{{ D eD|f D fk|f D fk{{ D fk|f D hR|f D hR{{ D hR|f D ix|f D ix{{ D ix|f D k_|f D k_{P D k_|f D mE|f D mE{{ D mE|f D nl|f D nl{{ D nl|f D pS|f D pS{{ D pS|f D qy|f D qy{{ D qy|f D s`|f D s`{P D s`|f D t]|f SR 1 HJ 3 SC M _?DN MK BF (Column \(pixels\)) WS 7 VJ M _?}\ MK BF (dev$pix: row 256) WS 3 SW 1 SC M KaHc D KfHd D KkHd D KpHd D KuHc D KzHf D K?Ha D LDHh D LJHf D LOHa D LTHf D LYHd D L^Hb D LcHh D LhHf D LmHg D LsHh D LxHo D L}Hk D MBHo D MGHt D MLHm D MQHp D MVHm D M\Ht D MaHo D MfHq D MkHt D MpHu D MuHq D MzHu D N@Hs D NEHq D NJHu D NOHx D NTHt D NYHx D N^Hz D NcHu D NiHt D NnHv D NsHy D NxHt D N}Hu D OBH} D OGIC D OLIB D ORIF D OWID D O\IA D OaID D OfII D OkIF D OpIG D OvIQ D O{IO D P@IR D PEIY D PJIY D POIb D PTIj D PYIl D P_Iq D PdI} D PiIv D PnIo D PsIh D PxId D P}Id D QBIl D QHIx D QMJG D QRJB D QWI} D Q\Iz D QaJA D QfI} D QkIs D QqIk D QvIo D Q{Io D R@Io D REIh D RJIj D ROIj D RTIm D RZIj D R_Id D RdId D RiI_ D RnIY D RsIZ D RxI[ D R}IU D SCI^ D SHI_ D SMIc D SRI^ D SWId D S\Ia D SaIa D SfIl D SlIx D SqIt D SvI~ D S{Iu D T@I{ D TEI{ D TJIy D TPIt D TUIv D TZIt D T_Is D TdIp D TiIu D TnI~ D TsI? D TyI? D T~I~ D UCJJ D UHJJ D UMJS D URJP D UWJJ D U\JK D UbJC D UgJG D UlJD D UqJB D UvJM D U{JX D V@Jd D VEJt D VKJ? D VPKT D VUKb D VZKb D V_Ke D VdKo D ViKy D VoKz D VtLB D VyLG D V~LT D WCLl D WHLx D WMLj D WRLT D WXKy D W]Kb D WbKP D WgKC D WlJ~ D WqJs D WvJf D W{J\ D XAJZ D XFJ\ D XKJX D XPJY D XUJU D XZJP D X_JS D XeJP D XjJC D XoJC D XtJC D XyJH D X~JK D YCJJ D YHJO D YNJT D YSJJ D YXJP D Y]JP D YbJ\ D YgJ_ D YlJa D YqJk D YwJt D Y|Jm D ZAJl D ZFJf D ZKJb D ZPJf D ZUJa D ZZJ\ D Z`Jc D ZeJk D ZjJh D ZoJc D ZtJa D ZyJX D Z~JL D [DJT D [IJP D [NJZ D [SJf D [XJo D []Jz D [bKG D [gK] D [lK\ D [rK] D [wKX D [|K] D \AKX D \FKY D \KKe D \PLB D \ULt D \[MW D \`My D \eNR D \jNS D \oOF D \tOe D \yNT D \?MB D ]DLN D ]IKo D ]NK~ D ]SLT D ]XMR D ]]Na D ]bO[ D ]hOs D ]mOh D ]rO_ D ]wOy D ]|Pv D ^AQw D ^FR[ D ^KRw D ^QSd D ^VTH D ^[TN D ^`Sw D ^eT` D ^jUQ D ^oVD D ^tV} D ^zWF D ^?WU D _DXK D _IZA D _N\\ D _S]l D _X]^ D _^]c D _c_f D _hbl D _me_ D _rgr D _wkN D _|r` D `Aza D `G|f D `LyK D `Qs` D `Vm@ D `[gr D ``c| D `e`m D `j^} D `p^j D `u^N D `z\\ D `?ZC D aDXh D aIX~ D aNZj D aT[W D aYZA D a^Vu D acTC D ahSf D amTp D arUV D awUP D a}TE D bBRR D bGPd D bLOM D bQN] D bVNa D b[Ny D b`NS D bfMB D bkLG D bpKl D buKl D bzKz D b?MB D cDNe D cIOt D cOPQ D cTOy D cYNt D c^Mj D ccLk D chLO D cmLN D csKx D cxK} D c}Kn D dBKn D dGKj D dLKc D dQKP D dVKA D d\Jt D daJm D dfJ^ D dkJa D dpJc D duJX D dzJQ D d?JF D eDJB D eJJJ D eOJK D eTJP D eYJT D e^JY D ecJZ D ehJP D enJQ D esJX D exJ^ D e}J] D fBJj D fGJg D fLJh D fQJ_ D fWJO D f\JL D faJO D ffJG D fkJJ D fpJO D fuJV D fzJQ D g@JU D gEJ] D gJJ\ D gOJZ D gTJc D gYJs D g^J| D gdJ| D giJz D gnJy D gsJ} D gxKH D g}KN D hBKC D hGJz D hMJu D hRJu D hWJy D h\Ju D haJm D hfJh D hkJ\ D hpJL D hvJJ D h{JT D i@Jf D iEJs D iJKA D iOKH D iTKS D iYKS D i_KN D idKN D iiKU D inKl D isLK D ixLu D i}Mn D jCOF D jHPV D jMPd D jRPm D jWPn D j\PJ D jaO\ D jfNS D jlME D jqLT D jvKz D j{Kb D k@KC D kEJp D kJJu D kOJ~ D kUJz D kZJv D k_Jv D kdJu D kiJm D knJl D ksJc D kyJY D k~J] D lCJQ D lHJH D lMJL D lRJG D lWIy D l\Iq D lbIl D lgIk D llIl D lqIm D lvIk D l{Iy D m@Im D mEI] D mKIk D mPIm D mUIo D mZIm D m_Ih D mdIh D miIa D mnIU D msI^ D myId D m~Id D nCIa D nHIc D nMI_ D nRI] D nWIj D n]Iv D nbIv D ngIx D nlIu D nqIk D nvIp D n{Il D o@Ic D oFI^ D oKI[ D oPIT D oUIM D oZIO D o_IR D odIT D oiIP D ooIO D otIR D oyIQ D o~IM D pCIM D pHIM D pMIQ D pSIT D pXIT D p]IR D pbIO D pgIM D plIK D pqIK D pvIK D p|IG D qAIH D qFII D qKIL D qPIL D qUIO D qZII D q_II D qeIL D qjIB D qoIB D qtIF D qyH} D q~Hy D rCHx D rHHz D rNHu D rSHx D rXHy D r]Hq D rbHp D rgHq D rlHp D rrHo D rwHk D r|Ho D sAHo D sFHk D sKHj D sPHk D sUHj D s[Hg D s`Hf D seHb D sjHh SR D soHk D stHd D syHb D s~Hb D tDHf D tIHf D tNHh D tSH] D tXHa D t]Ha SR %PSKCloseWorkStation %PSKOpenWorkStation M KaH_ D KfH^ D KkHc D KpHd D KuH_ D KzHc D K?H] D LDHa D LJHd D LOHa D LTHd D LYHa D L^Hg D LcHc D LhHk D LmHf D LsHg D LxHk D L}Hk D MBHo D MGHo D MLHj D MQHp D MVHt D M\Hq D MaHu D MfHv D MkHs D MpHu D MuHy D MzHv D N@Ht D NEHv D NJHv D NOHc D NQH] SR M NUH] D NYI] D N^I{ D NcIK D NiHu D NnHu D NsHz D NxHv D N}Hx D OBHv D OGHv D OLH? D ORH~ D OWH~ D O\IF D OaIC D OfIF D OkIU D OpI_ D OvIg D O{Ia D P@IZ D PEI[ D PJI^ D POIg D PTIs D PYJD D P_JM D PdJB D PiIs D PnIm D PsJB D PxJb D P}Jt D QBJf D QHJY D QMJM D QRJP D QWJX D Q\J\ D QaJ] D QfJY D QkJ^ D QqJV D QvJP D Q{JD D R@J] D REJg D RJJk D ROJP D RTIt D RZId D R_Ia D RdIX D RiIQ D RnIO D RsIM D RxIV D R}I] D SCI^ D SHIc D SMId D SRI[ D SWI^ D S\Id D SaIj D SfIo D SlIq D SqIm D SvIo D S{Iq D T@I~ D TEJH D TJI~ D TPI~ D TUI{ D TZI? D T_JA D TdI} D TiI{ D TnIu D TsIt D TyIt D T~Ip D UCIp D UHIs D UMIv D URI~ D UWJC D U\JJ D UbJP D UgJ\ D UlJb D UqJc D UvJc D U{Jd D V@Jh D VEJs D VKJ| D VPKP D VUKe D VZKw D V_LQ D VdLg D ViLe D VoLa D VtL\ D VyLa D V~L} D WCN@ D WHOA D WMNm D WRMc D WXLk D W]Lb D WbML D WgMj D WlM[ D WqLx D WvLU D W{Kt D XAKY D XFKH D XKJy D XPJl D XUJb D XZJS D X_JJ D XeJG D XjJA D XoJC D XtJS D XyJ\ D X~JS D YCJK D YHJH D YNJH D YSJJ D YXJY D Y]Ja D YbJd D YgJk D YlJf D YqJb D YwJ\ D Y|Jb D ZAJp D ZFJ} D ZKJ} D ZPJq D ZUJf D ZZJb D Z`JQ D ZeJU D ZjJM D ZoJT D ZtJU D ZyJO D Z~JT D [DJH D [IJH D [NJJ D [SJX D [XJd D []Jz D [bJ? D [gKJ D [lKJ D [rK] D [wLH D [|LU D \ALc D \FLi D \KLt D \PLc D \UL` D \[L} D \`MR D \eMb D \jNS D \oOD D \tNa D \yNO D \?Mu D ]DMC D ]ILl D ]NLT D ]SLA D ]XLF D ]]LU D ]bLS D ]hLY D ]mLZ D ]rLk D ]wMa D ]|OI D ^AQO D ^FSJ D ^KTA D ^QSq D ^VSq D ^[Sv D ^`So D ^eSx D ^jTG D ^oTd D ^tT_ D ^zTc D ^?Ti D _DU~ D _IX@ D _NXy D _SXu D _XXP D _^X@ D _cWu D _hWr D _mX[ D _rYp D _w[E D _|[t D `A\A D `G[z D `L[| D `Q\B D `V\L D `[\g D ``\g D `e[X D `jYZ D `pWr D `uV` D `zTw D `?SR D aDS^ D aITQ D aNSc D aTRQ D aYRA D a^RA D acP? D ahPB D amO` D arOi D awQ@ D a}RV D bBRw D bGRo D bLQn D bQP@ D bVN? D b[Nk D b`Mo D bfLg D bkKl D bpKc D buKb D bzKk D b?Kx D cDLY D cILs D cOLo D cTL] D cYK| D c^LE D ccLH D chLP D cmLk D csLa D cxLA D c}Kl D dBKb D dGKJ D dLKB D dQKB D dVJv D d\Jp D daJY D dfJS D dkJV D dpJU D duJJ D dzJT D d?JZ D eDJQ D eJJJ D eOJK D eTJH D eYJB D e^JD D ecJL D ehJJ D enI~ D esI~ D exI? D e}JF D fBJQ D fGJ\ D fLJc D fQJh D fWJm D f\J_ D faJZ D ffJ^ D fkJU D fpJU D fuJK D fzJF D g@JF D gEJK D gJJJ D gOJG D gTJT D gYJb D g^Jj D gdJu D giJu D gnJu D gsJ? D gxKA D g}J? D hBJ~ D hGKA D hMKG D hRKJ D hWKN D h\KO D haKO D hfKH D hkJ| D hpJv D hvJu D h{Jl D i@Jb D iEJl D iJJu D iOJ? D iTKN D iYKQ D i_KX D idKY D iiKV D inKk D isLQ D ixM@ D i}MP D jCMF D jHLu D jMLi D jRLo D jWM[ D j\Mn D jaM[ D jfMA D jlLo D jqLX D jvLA D j{Kk D k@KZ D kEKY D kJK_ D kOKk D kUKx D kZKq D k_K_ D kdKH D kiJp D knJt D ksKN D kyKh D k~Kh D lCKN D lHKA D lMJ} D lRJk D lWJ^ D l\JV D lbJO D lgJF D llJB D lqIx D lvIq D l{Ip D m@Io D mEIl D mKIo D mPIh D mUIc D mZIt D m_Ix D mdIx D miI~ D mnJB D msJL D myJB D m~Iv D nCIv D nHIo D nMIc D nRIZ D nWIa D n]If D nbIk D ngIl D nlIu D nqI{ D nvJH D n{JZ D o@JU D oFJG D oKJF D oPI{ D oUIt D oZIp D o_Io D odIs D oiIl D ooIj D otI_ D oyIO D o~IF D pCIC D pHIP D pMIZ D pSIa D pXIf D p]Ij D pbIc D pgIX D plIY D pqIX D pvIO D p|IK D qAIH D qFID D qKIG D qPIF D qUIF D qZIB D q_H? D qeID D qjIF D qoIB D qtH? D qyIB D q~H~ D rCH} D rHHz D rNH| D rSH} D rXHv D r]Hv D rbHs D rgHv D rlHq D rrHo D rwHo D r|Ho D sAHm D sFHk D sKHf D sPHh D sUHh D s[Hh D s`Hg D seHk D sjHj D soHh D stHj D syHj D s~Hb D tDHg D tIHc D tNHa D tSHf D tXH_ D t]Ha SR %PSKCloseWorkStation %PSKOpenWorkStation M KaHa D KfHc D KkHd D KpHd D KuHh D KzH_ D K?Hb D LDH] D LJHb D LOHb D LTHc D LYHk D L^Hf D LcHh D LhHc D LmHg D LsHf D LxHc D L}Hh D MBHf D MGHj D MLHj D MQHb D MVHh D M\Hj D MaHc D MfHk D MkHg D MpHl D MuHl D MzHl D N@Hm D NEHl D NJHm D NOHo D NTHq D NYHk D N^Hm D NcHp D NiHo D NnHu D NsHu D NxHt D N}Hx D OBHy D OGH| D OLH| D ORHx D OWHv D O\Hz D OaH~ D OfIA D OkHz D OpHy D OvIG D O{IO D P@IM D PEIQ D PJIQ D POIO D PTIO D PYIM D P_IU D PdI_ D PiI_ D PnI_ D PsI[ D PxI^ D P}Ib D QBI[ D QHIY D QMI] D QRId D QWIo D Q\Iu D QaIm D QfIj D QkIc D QqI] D QvIZ D Q{IP D R@IQ D REIU D RJIY D ROI_ D RTI^ D RZIj D R_Ib D RdIY D RiIf D RnI] D RsIb D RxI] D R}I_ D SCI^ D SHId D SMI] D SRI] D SWIV D S\IZ D SaIa D SfIQ D SlIb D SqI] D SvIX D S{Ic D T@Ib D TEI[ D TJI_ D TPI] D TUIZ D TZIT D T_IZ D TdIa D TiIX D TnIX D TsIU D TyIP D T~IZ D UCIP D UHIZ D UMIR D URIL D UWIK D U\IH D UbIM D UgIO D UlIP D UqIP D UvIL D U{IP D V@IR D VEIT D VKI[ D VPI[ D VUIY D VZIX D V_I] D VdIX D ViIZ D VoIb D VtIc D VyIh D V~Il D WCIo D WHIk D WMIk D WRIt D WXI? D W]JH D WbJL D WgJL D WlJQ D WqJZ D WvJ_ D W{Jd D XAJl D XFJv D XKKE D XPKF D XUKF D XZKP D X_KV D XeKa D XjKg D XoLB D XtL] D XyLw D X~MO D YCMK D YHMT D YNM^ D YSMT D YXM[ D Y]Mx D YbNI D YgNE D YlM} D YqMk D YwMU D Y|MG D ZALs D ZFLc D ZKLY D ZPLG D ZUK} D ZZKx D Z`KZ D ZeKT D ZjKB D ZoJz D ZtJp D ZyJ^ D Z~JY D [DJ^ D [IJd D [NJp D [SJc D [XJc D []J\ D [bJ_ D [gJ] D [lJV D [rJV D [wJ^ D [|Jm D \AJV D \FJa D \KJj D \PJh D \UJb D \[Jm D \`J| D \eJ~ D \jJu D \oJ| D \tKQ D \yKF D \?KF D ]DKP D ]IKB D ]NJy D ]SJ~ D ]XJp D ]]Js D ]bJa D ]hJm D ]mJd D ]rJh D ]wJv D ]|Jx D ^AJ~ D ^FJv D ^KJ} D ^QJ? D ^VJ? D ^[KE D ^`KN D ^eKN D ^jK| D ^oLT D ^tKa D ^zJm D ^?JV D _DJ\ D _IJ_ D _NJ_ D _SJd D _XJm D _^Jm D _cJf D _hJc D _mJh D _rJo D _wJk D _|Jh D `AJo D `GJt D `LJo D `QJd D `VJf D `[Jc D ``J\ D `eJQ D `jJP D `pJU D `uJV D `zJb D `?JX D aDJM D aIJC D aNI~ D aTIz D aYIz D a^I} D acI{ D ahIy D amI? D arI} D awIy D a}I} D bBJM D bGJS D bLJa D bQJp D bVJq D b[J? D b`Jo D bfJg D bkJh D bpJb D buJh D bzJ| D b?KE D cDJo D cIJm D cOJm D cTJu D cYJl D c^J_ D ccJ] D chJZ D cmJ\ D csJS D cxJV D c}JQ D dBJF D dGJL D dLJC D dQJO D dVI~ D d\JL D daJL D dfJB D dkIz D dpI} D duIs D dzIx D d?It D eDIf D eJIc D eOIg D eTIp D eYIv D e^Iu D ecJF D ehJL D enJC D esI} D exI} D e}JJ D fBJB D fGIx D fLI{ D fQI{ D fWIs D f\Io D faJC D ffJA D fkJA D fpJG D fuJC D fzJF D g@JH D gEJO D gJJX D gOJa D gTJm D gYJs D g^Jo D gdJo D giJk D gnJd D gsJU D gxJK D g}JJ D hBJL D hGJ\ D hMJj D hRJm D hWJk D h\Jm D haJg D hfJd D hkJl D hpJg D hvJc D h{Ja D i@Jt D iEKE D iJKN D iOKV D iTKV D iYKf D i_Kh D idKk D iiKk D inKe D isKo D ixKn D i}Kg D jCKz D jHKk D jMKa D jRKL D jWKL D j\KK D jaJ~ D jfJp D jlJc D jqJS D jvJF D j{JB D k@JB D kEJL D kJJ^ D kOJf D kUJd D kZJo D k_Jx D kdJl D kiJb D knJX D ksJA D kyIx D k~Iq D lCIg D lHIh D lMIP D lRI] D lWIU D l\I] D lbIV D lgI^ D llIU D lqIT D lvIT D l{IU D m@IR D mEIT D mKIY D mPI[ D mUIZ D mZIY D m_I] D mdIa D miIX D mnIZ D msIT D myIZ D m~IY D nCIR D nHIR D nMIO D nRIT D nWIX D n]IV D nbIT D ngIY D nlIY D nqIZ D nvI] D n{Id D o@Ib D oFI_ D oKIh D oPIj D oUIb D oZIa D o_Ip D odI? D oiI{ D ooI? D otIx D oyIk D o~I^ D pCIX D pHI[ D pMI] D pSIT D pXIK D p]II D pbIF D pgIF D plIM D pqIM D pvIM D p|IP D qAIU D qFI] D qKI[ D qPIb D qUId D qZI] D q_IY D qeI[ D qjIU D qoIV D qtIX D qyIK D q~IH D rCIL D rHIG D rNIG D rSIF D rXIC D r]IA D rbIB D rgIA D rlHz D rrIA D rwH? D r|H? D sAHv D sFHt D sKHq D sPHm D sUHu D s[Hq D s`Hm D seHl D sjHl SR D soHh D stHh D syHo D s~Ho D tDHk D tIHh D tNHk D tSHk D tXHh D t]Hl SR %PSKCloseWorkStation %PSKOpenWorkStation CL %%Trailer R R %DocumentFonts: Times-Bold Times-Italic Symbol Times-Roman %%Pages: 1 %%EOF ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_basic.py���������������������������������������������������������������0000644�0001750�0001750�00000004014�14203121554�016334� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Few simple tests based on Preliminary Test Procedure for IRAF, IRAF Version V2.11, Jeannette Barnes, Central Computer Services, National Optical Astronomy Observatories, P.O. Box 26732, Tucson, AZ 85726, Revised September 23, 1997. These tests are in no mean complete; they shall just test the interaction between Pyraf and IRAF. """ import io import os import pytest from astropy.io import fits from .utils import HAS_IRAF if HAS_IRAF: from pyraf import iraf else: pytestmark = pytest.mark.skip('Need IRAF to run') def teardown_module(self): files_to_del = [ 'image.real.fits', 'image.dbl.fits', 'image.short.fits' ] for fname in files_to_del: if os.path.exists(fname): os.remove(fname) def test_imcopy(): iraf.imcopy('dev$pix', 'image.short', verbose=False) with fits.open('image.short.fits') as f: assert len(f) == 1 assert f[0].header['BITPIX'] == 16 assert (f[0].header['ORIGIN'] == 'NOAO-IRAF FITS Image Kernel July 2003') assert f[0].data.shape == (512, 512) def test_imhead(): out = io.StringIO() iraf.imhead('dev$pix', Stdout=out) assert out.getvalue() == 'dev$pix[512,512][short]: m51 B 600s\n' def test_imarith(): iraf.imarith('dev$pix', '/', '1', 'image.real', pixtype='r') with fits.open('image.real.fits') as f: assert f[0].header['BITPIX'] == -32 assert f[0].data.shape == (512, 512) iraf.imarith('dev$pix', '/', '1', 'image.dbl', pixtype='d') with fits.open('image.dbl.fits') as f: assert f[0].header['BITPIX'] == -64 assert f[0].data.shape == (512, 512) def test_hedit(): if os.path.exists('image.real.fits'): os.remove('image.real.fits') iraf.imarith('dev$pix', '/', '1', 'image.real', pixtype='r') iraf.hedit('image.real', 'title', 'm51 real', verify=False, Stdout="/dev/null") with fits.open('image.real.fits') as f: assert f[0].header['OBJECT'] == 'm51 real' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1644995436.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_clcache.py�������������������������������������������������������������0000644�0001750�0001750�00000002170�14203121554�016636� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os import pytest from pyraf.clcache import _CodeCache # simple class to mimic pycode, for unit test (save us from importing others) class DummyCodeObj: def setFilename(self, f): self.filename = f def __str__(self): retval = '<DummyCodeObj:' if hasattr(self, 'filename'): retval += ' filename="' + self.filename + '"' if hasattr(self, 'code'): retval += ' code="' + self.code + '"' retval += '>' return retval def test_codecache(tmpdir): # Dummy file for caching test fname = 'dummyfile.py' f = tmpdir.join(fname) f.write('Hello world\n') codeCache = _CodeCache([os.path.join(tmpdir.strpath, 'clcache')]) fpath = str(f) idx = codeCache.getIndex(fpath) pc = DummyCodeObj() pc.code = 'print(123)' codeCache.add(idx, pc) # goes in here codeCache.add(idx, pc) # NOT duplicated here assert len(codeCache.cacheList) == 1 assert list(codeCache.clFileDict.keys())[0].endswith(fname) newidx, newpycode = codeCache.get(fpath) assert newidx == idx assert isinstance(newpycode, DummyCodeObj) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647614094.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_cli.py�����������������������������������������������������������������0000644�0001750�0001750�00000030360�14215114216�016025� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""These were tests under cli in pandokia.""" import io import math from contextlib import contextmanager, redirect_stderr import pytest from .utils import HAS_IRAF from .. import iraf from .. import sscanf from .. import pyrafglobals @contextmanager def use_ecl(flag): """Temporary enable/disable usage of ECL""" # This is a quite dirty hack that is adjusted to do just enough to # let the tests here pass. It is not meant to be used outside. # Re-initialization of IRAF takes more that what is done here. if flag != pyrafglobals._use_ecl: old_ecl = pyrafglobals._use_ecl pyrafglobals._use_ecl = flag from .. import iraffunctions iraffunctions._pkgs.clear() iraffunctions._tasks.clear() iraf.Init(doprint=False, hush=True) yield if flag != pyrafglobals._use_ecl: pyrafglobals._use_ecl = old_ecl iraffunctions._pkgs.clear() iraffunctions._tasks.clear() iraf.Init(doprint=False, hush=True) @pytest.mark.parametrize('ecl_flag', [False, True]) def test_division_task(ecl_flag): # Show how a .cl script would be run with use_ecl(ecl_flag): stdout = io.StringIO() iraf.task(xyz='print "e: " (9/5)\nprint "f: " (9/5.)\n' 'print "g: " (9//5)\nprint "h: " (9//5.)', IsCmdString=True) iraf.xyz(StdoutAppend=stdout) assert stdout.getvalue() == "e: 1\nf: 1.8\ng: 95\nh: 95.0\n" @pytest.mark.parametrize('arg,expected', [ ('9/5', '1'), ('9/5.', '1.8'), ('9//5', '95'), # string concatenation ('9//5.', '95.0'), # string concatenation ]) @pytest.mark.parametrize('ecl_flag', [False, True]) def test_division(arg, expected, ecl_flag): with use_ecl(ecl_flag): stdout = io.StringIO() iraf.clExecute(f'print "i: " ({arg})', StdoutAppend=stdout) assert stdout.getvalue().strip() == f'i: {expected}' @pytest.mark.parametrize('arg,fmt,expected', [ ("seven 6 4.0 -7", "%s %d %g %d", ['seven', 6, 4.0, -7]), # aliveness ("seven", "%d", []), ("seven", "%c%3c%99c", ['s', 'eve', 'n']), ("0xabc90", "%x", [703632]), ]) def test_sscanf(arg, fmt, expected): """A basic unit test that sscanf was built/imported correctly and can run. """ l = sscanf.sscanf(arg, fmt) assert l == expected def test_sscanf_error(): # API error with pytest.raises(TypeError): sscanf.sscanf() @pytest.mark.skipif(not HAS_IRAF, reason='Need IRAF to run') @pytest.mark.parametrize('arg,expected', [ ("pw", "user.pwd"), ("lang", "clpackage.language"), ("st", "plot.stdplot plot.stdgraph user.strings imcoords.starfind"), ("std", "plot.stdplot plot.stdgraph"), ("stdg", "plot.stdgraph"), ("stdpl", "plot.stdplot"), ("star", "imcoords.starfind"), ("star man", "imcoords.starfind user.man"), ("vi", "tv.vimexam user.vi"), ("noao", "clpackage.noao"), ("impl", "plot.implot"), ("ls", "user.ls"), ("surf", "plot.surface noao.surfphot utilities.surfit"), ("surf man", "plot.surface noao.surfphot utilities.surfit user.man"), ("smart man", "user.man"), ("img", "imutil.imgets images.imgeom"), ("prot", "system.protect clpackage.proto"), ("pro", "plot.prows plot.prow system.protect clpackage.proto"), ("prow", "plot.prows plot.prow"), ("prows", "plot.prows"), ("prowss", None), ("dis", "tv.display system.diskspace"), ("no", "noao.nobsolete clpackage.noao"), ]) def test_whereis(arg, expected): iraf.plot(_doprint=0) iraf.images(_doprint=0) stdout = io.StringIO() args = arg.split(" ") # convert to a list kw = {'StderrAppend': stdout} iraf.whereis(*args, **kw) # catches stdout+err # Check that we get one line per argument assert len(stdout.getvalue().splitlines()) == len(args) # Check that each argument appears in the coresponding line for res, a in zip(stdout.getvalue().splitlines(), args): assert a in res # If task not found if expected is None: assert f"{arg}: task not found." in stdout.getvalue() else: # Check that all expected found places are returned for f in expected.split(): assert f in stdout.getvalue() @pytest.mark.skipif(not HAS_IRAF, reason='Need IRAF to run') @pytest.mark.parametrize('arg, expected', [ ("pw", "user"), ("lang", "clpackage"), ("stdg", "plot"), ("stdp", "plot"), ("star", "imcoords"), ("star man", "imcoords\nuser"), ("vi", "user"), ("noao", "clpackage"), ("impl", "plot"), ("ls", "user"), ("surf", "\"Task `surf' is ambiguous, could be " "utilities.surfit, noao.surfphot, plot.surface\""), ("surface", "plot"), ("img", "\"Task `img' is ambiguous, could be " "images.imgeom, imutil.imgets\""), ("pro", "\"Task `pro' is ambiguous, could be " "clpackage.proto, system.protect, plot.prow, ...\""), ("prot", "\"Task `prot' is ambiguous, could be " "clpackage.proto, system.protect\""), ("prow", "plot"), ("prows", "plot"), ("prowss", "prowss: task not found."), ("dis", "\"Task `dis' is ambiguous, could be " "system.diskspace, tv.display\""), ]) def test_which(arg, expected): iraf.plot(_doprint=0) iraf.images(_doprint=0) # To Test: normal case, disambiguation, ambiguous, not found, multiple # inputs for a single command stdout = io.StringIO() args = arg.split(" ") # convert to a list kw = {"StderrAppend": stdout} iraf.which(*args, **kw) # catches stdout+err actual_lines = stdout.getvalue().strip().splitlines() expected_lines = expected.splitlines() assert len(actual_lines) == len(expected_lines) for arg, actual_line, expected_line in zip(args, actual_lines, expected_lines): if 'ambiguous' in actual_line: if 'ambiguous' in expected_line: assert actual_line.startswith(expected_line.rstrip('"')) else: assert f'{expected_line}.{arg}' in actual_line else: assert actual_line == expected_line def test_parse_cl_array_subscripts(): # Check that square brackets are parsed correctly # https://github.com/iraf-community/pyraf/issues/115 stdout = io.StringIO() iraf.task(xyz='char cenwavvalue[4]\n' 'cenwavvalue[1] = "580"\n' 'print(cenwavvalue[1])', IsCmdString=True) iraf.xyz(StdoutAppend=stdout) assert stdout.getvalue() == "580\n" @pytest.mark.parametrize('call, expected', [ ('acos(0.67)', math.acos(0.67)), ('asin(0.67)', math.asin(0.67)), ('atan2(2.,3.)', math.atan2(2.,3.)), ('cos(1.2)', math.cos(1.2)), ('dacos(0.67)', math.degrees(math.acos(0.67))), ('dasin(0.67)', math.degrees(math.asin(0.67))), ('datan2(2.,3.)', math.degrees(math.atan2(2.,3.))), ('dcos(12.)', math.cos(math.radians(12.))), ('deg(1.2)', math.degrees(1.2)), ('dsin(12.)', math.sin(math.radians(12.))), ('dtan(12.)', math.tan(math.radians(12.))), ('exp(1.2)', math.exp(1.2)), ('frac(1.2)', 1.2 % 1), ('hypot(2.,3.)', math.hypot(2.,3.)), ('int(-1.2)', int(-1.2)), ('isindef(0)', False), ('isindef(INDEF)', True), ('log(1.2)', math.log(1.2)), ('log10(1.2)', math.log10(1.2)), ('max(2,3,4,2)', max(2,3,4,2)), ('min(2,3,4,2)', min(2,3,4,2)), ('mod(123, 7)', 123 % 7), ('nint(1.5)', 2), ('nint(2.5)', 3), ('rad(12)', math.radians(12)), ('radix(123, 10)', '123'), ('radix(123, 16)', '7B'), ('radix(-123, 10)', '4294967173'), ('radix(-123, 16)', 'FFFFFF85'), ('real("1.23")', 1.23), ('sign(-1.2)', -1), ('sign(1.2)', 1), ('sin(1.2)', math.sin(1.2)), ('sqrt(1.2)', math.sqrt(1.2)), ('stridx("fd", "abcdefg")', 4), ('strldx("fd", "abcdefg")', 6), ('strlen("abcdefg")', 7), ('strlstr("def", "abcdefg")', 4), ('strlwr("aBcDeF")', "abcdef"), ('strstr("def", "abcdefg")', 4), ('strupr("aBcDeF")', "ABCDEF"), ('substr("abcdefg", 2, 5)', "bcde"), ('tan(1.2)', math.tan(1.2)), ('trim("--abcdefg---", "-")', "abcdefg"), ('triml("--abcdefg---", "-")', "abcdefg---"), ('trimr("--abcdefg---", "-")', "--abcdefg"), ]) def test_intrinsic_functions(call, expected): with redirect_stderr(io.StringIO()): # silence task redefinition warnings iraf.task(xyz=f'print({call})', IsCmdString=True) stdout = io.StringIO() iraf.xyz(Stdout=stdout) if isinstance(expected, float): assert float(stdout.getvalue().strip()) == pytest.approx(expected, 1e-8) elif isinstance(expected, int): assert int(stdout.getvalue().strip()) == expected else: assert stdout.getvalue().strip() == expected @pytest.mark.parametrize('ecl_flag', [False, True]) @pytest.mark.parametrize('encoding', ['utf-8', 'iso-8859-1']) @pytest.mark.parametrize('code,expected', [ ('print AA # Ångström\n', 'AA\n'), ('print("test") | cat', 'test\n'), ]) def test_clfile(tmpdir, ecl_flag, encoding, code, expected): # Check proper reading of CL files with different encodings fname = tmpdir / 'cltestfile.cl' with fname.open("w", encoding=encoding) as fp: fp.write(code) stdout = io.StringIO() with use_ecl(ecl_flag): iraf.task(xyz=str(fname)) iraf.xyz(StdoutAppend=stdout) assert stdout.getvalue() == expected @pytest.mark.skipif(not HAS_IRAF, reason='Need IRAF to run') @pytest.mark.parametrize('ecl_flag', [False, True]) @pytest.mark.parametrize('code', [ 'cat {datafile}', # foreign 'type {datafile} map_cc=no', # internal 'concatenate {datafile} out_type=t', # system ]) @pytest.mark.parametrize('data', [ b'test for plain ASCII', 'Unicode Ångström'.encode('utf-8'), b'\x8fq\x96\x7f\xe6\xfd\xf8\x12\xb03{U\x81\x11\x014', # some random data ]) @pytest.mark.parametrize('redir_stdin', [False, True]) @pytest.mark.parametrize('redir_stdout', [False, True]) def test_redir_data(tmpdir, ecl_flag, code, data, redir_stdin, redir_stdout): datafile = tmpdir / 'cltestdata.dat' with datafile.open('wb') as fp: fp.write(data) if redir_stdin: code = code.format(datafile='') + f' < {str(datafile)}' else: code = code.format(datafile=str(datafile)) if redir_stdout: outfile = tmpdir / 'cltestoutput.dat' code += f' > {str(outfile)}' with use_ecl(ecl_flag): iraf.task(xyz=code, IsCmdString=True) stdout = io.TextIOWrapper(io.BytesIO()) # Emulate a "real" stdout iraf.xyz(Stdout=stdout) if redir_stdout: with open(outfile, 'rb') as fp: assert fp.read() == data else: assert stdout.buffer.getvalue() == data @pytest.mark.skipif(not HAS_IRAF, reason='Need IRAF to run') def test_binary_stdout(tmpdir): # Test writing binary data to STDOUT, one of several issues mentioned in # https://github.com/iraf-community/pyraf/issues/117 outfile = str(tmpdir / 'testout.gki') expected = [ f"METAFILE '{outfile}':", '[1] (2855 words) The SINC Function', '[2] (5701 words) .2', '[3] (2525 words) Line 250 of dev$pix[200:300,*]', '[4] (7637 words) Log Scaling', '[5] (97781 words) NOAO/IRAF V2.3 tody@lyra Fri 23:30:27 08-Aug-86', '[6] (2501 words) The Sinc Function' ] iraf.gkiextract('dev$vdm.gki', '2-7', iraf.yes, verify=False, Stdout=outfile) stdout = io.StringIO() iraf.gkidir(outfile, Stdout=stdout) # Require gkidir output to match list above, ignoring spacing: assert [' '.join(line.split()) for line in stdout.getvalue().splitlines() if line] == expected def test_real_parameter_str_precision(): # Check that PyRAF uses the same precision for real parameters on Python 3 # as on 2 (see https://github.com/iraf-community/pyraf/issues/127) iraf.task( print_real='''procedure print_real(value) real value begin print(value) end''', IsCmdString=True ) stdout = io.StringIO() iraf.print_real(1.2345678901234567, Stdout=stdout) assert stdout.getvalue() == "1.23456789012\n" stdout = io.StringIO() iraf.print_real(1000000000000000.0, Stdout=stdout) assert stdout.getvalue() == "1e+15\n" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_core_nongraphics.py����������������������������������������������������0000644�0001750�0001750�00000017533�14214070451�020611� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""These were tests under core/irafparlist and core/subproc in pandokia.""" import time import uuid import pytest from .utils import HAS_IRAF from ..irafpar import IrafParList from ..subproc import Subprocess from ..tools import basicpar from ..tools.basicpar import parFactory @pytest.fixture def _proc(): return Subprocess('cat', expire_noisily=0) def test_subproc_wait(): proc = Subprocess('true', 1, expire_noisily=0) assert proc.wait(1), 'How is this still alive after 1 sec?' def test_subproc_write_readline(_proc): """Buffer readback test; readline """ _proc.write(b'test string one\n') _proc.write(b'test string two\n') time.sleep(0.1) assert _proc.readline() == b'test string one\n' assert _proc.readline() == b'test string two\n' def test_subproc_write_readPendingChars(_proc): """Buffer readback test; readPendingChars """ test_inputs = (b'one', b'two', b'three') for test_input in test_inputs: _proc.write(test_input + b'\n') time.sleep(0.1) expected = tuple(_proc.readPendingChars().splitlines()) assert test_inputs == expected def test_subproc_stop_resume(_proc): """Ensure we can stop and resume a process """ assert _proc.stop() assert _proc.cont() def test_subproc_stop_resume_write_read(_proc): """Ensure we can stop the process, write data to the pipe, resume, then read the buffered data back from the pipe. """ assert _proc.stop() _proc.write(b'test string\n') assert not len(_proc.readPendingChars()) assert _proc.cont() assert _proc.readline() == b'test string\n' def test_subproc_kill_via_delete(_proc): """Kill the process by deleting the instance. We cannot assert anything, but an exception will bomb this out. """ del _proc def test_subproc_kill_via_die(_proc): """Kill the process the "right way." """ assert _proc.pid is not None _proc.die() assert _proc.pid is None @pytest.fixture def _ipl_defaults(tmpdir): defaults = dict(name='bobs_pizza', filename=str(tmpdir.join('bobs_pizza.par'))) return defaults @pytest.fixture def _ipl(_ipl_defaults): """An IrafParList object """ return IrafParList(_ipl_defaults['name'], _ipl_defaults['filename']) @pytest.fixture def _pars(): values = ( (('caller', 's', 'a', 'Ima Hungry', '', None, 'person calling Bobs'), True), (('diameter', 'i', 'a', '12', '', None, 'pizza size'), True), (('pi', 'r', 'a', '3.14159', '', None, 'Bob makes circles!'), True), (('delivery', 'b', 'a', 'yes', '', None, 'delivery? (or pickup)'), True), (('topping', 's', 'a', 'peps', '|toms|peps|olives', None, 'the choices'), True), ) return [parFactory(*x) for x in values] def test_irafparlist_getName(_ipl, _ipl_defaults): assert _ipl.getName() == _ipl_defaults['name'] def test_irafparlist_getFilename(_ipl, _ipl_defaults): assert _ipl.getFilename() == _ipl_defaults['filename'] def test_irafparlist_getPkgname(_ipl, _ipl_defaults): assert not _ipl.getPkgname() def test_irafparlist_hasPar_defaults(_ipl, _ipl_defaults): assert _ipl.hasPar('$nargs') assert _ipl.hasPar('mode') assert len(_ipl) == 2 def test_irafparlist_addParam_verify(_ipl, _pars): """Add a series of pars to _ipl while verifying the data can be read back as it appears. """ for idx, par in enumerate(_pars, start=1): # Probably pointless. assert par.dpar().strip() == \ f"{par.name} = {par.toString(par.value, quoted=1)}" # Add the paramater (+2 takes each par's default values into account) _ipl.addParam(par) assert len(_ipl) == idx + 2 # Check that each par name exists in _ipl, plus defaults defaults = ['$nargs', 'mode'] solution = sorted([x.name for x in _pars[:idx]] + defaults) assert sorted(_ipl.getAllMatches('')) == solution def test_irafparlist_getAllMatches(_ipl, _pars): for par in _pars: _ipl.addParam(par) # Check that each par name exists in _ipl defaults = ['$nargs', 'mode'] solution = sorted([x.name for x in _pars] + defaults) assert sorted(_ipl.getAllMatches('')) == solution def test_irafparlist_getAllMatches_known_needle(_ipl, _pars): """Expect to receive a list of pars starting with needle """ for par in _pars: _ipl.addParam(par) needle = 'd' # Verify the pars returned by getAllMatches(needle) are correct solution = sorted( str(x.name) for x in _pars if str(x.name).startswith(needle)) assert sorted(_ipl.getAllMatches(needle)) == solution def test_irafparlist_getAllMatches_unknown_needle(_ipl, _pars): """Expect to receive an empty list with no par match """ for par in _pars: _ipl.addParam(par) needle = 'jojo' assert sorted(_ipl.getAllMatches(needle)) == list() def test_irafparlist_getParDict(_ipl, _pars): for par in _pars: _ipl.addParam(par) par_dict = _ipl.getParDict() for par in _pars: assert par.name in par_dict def test_irafparlist_getParList(_ipl, _pars): for par in _pars: _ipl.addParam(par) par_list = _ipl.getParList() for par in _pars: assert par in par_list def test_irafparlist_hasPar(_ipl, _pars): for par in _pars: _ipl.addParam(par) for par in _pars: assert _ipl.hasPar(par.name) def test_irafparlist_setParam_string(_ipl, _pars): """Change existing parameter then verify it """ # Use the first string parameter we come across for par in _pars: if par.type == 's': assert isinstance(par, basicpar.IrafParS) break _ipl.addParam(par) _ipl.setParam(par.name, 'different value') assert 'different value' == _ipl.getParDict()[par.name].value def test_irafparlist_setParam_integer(_ipl, _pars): """Change existing parameter then verify it """ # Use the first integer parameter we come across for par in _pars: if par.type == 'i': assert isinstance(par, basicpar.IrafParI) break _ipl.addParam(par) new_value = par.value + 1 _ipl.setParam(par.name, new_value) assert new_value == _ipl.getParDict()[par.name].value def test_irafparlist_setParam_float(_ipl, _pars): """Change existing parameter then verify it """ # Use the first integer parameter we come across for par in _pars: if par.type == 'r': assert isinstance(par, basicpar.IrafParR) break _ipl.addParam(par) new_value = par.value + 1.0 _ipl.setParam(par.name, new_value) assert new_value == _ipl.getParDict()[par.name].value @pytest.mark.xfail(reason='Can overwrite string type with uncast integer') def test_irafparlist_incompatible_assignment_raises(_ipl, _pars): """Assign incompatible values to existing pars """ bsint = uuid.uuid1().hex[:8] break_with = dict( s=int(bsint, 16), b=bsint, i=bsint, l=bsint, f=bsint, r=bsint, ) for par in _pars: _ipl.addParam(par) for par in _pars: with pytest.raises(ValueError): setattr(_ipl, par.name, break_with[par.type]) def test_irafparlist_boolean_convert_false(_ipl, _pars): # Select first boolean par for par in _pars: if par.type == 'b': break _ipl.addParam(par) test_inputs = (False, 0, 'NO') for test_input in test_inputs: setattr(_ipl, par.name, test_input) assert getattr(_ipl, par.name) == 'no' def test_irafparlist_boolean_convert_true(_ipl, _pars): # Select first boolean par for par in _pars: if par.type == 'b': break _ipl.addParam(par) test_inputs = (True, 1, 'YES') for test_input in test_inputs: setattr(_ipl, par.name, test_input) assert getattr(_ipl, par.name) == 'yes' ���������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_graphics.py������������������������������������������������������������0000644�0001750�0001750�00000030105�14214070451�017054� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""These were tests under core/graphics_checks in pandokia.""" import io import glob import os import sys import time import pytest try: host_arch = sys.implementation._multiarch.split("-")[0] except AttributeError: import platform host_arch = platform.machine() is_i386 = host_arch in ('i386', 'i486', 'i586', 'i686') from ..tools import capable from .utils import HAS_IRAF, DATA_DIR if HAS_IRAF: from pyraf import iraf from pyraf import gki from pyraf import wutil else: pytestmark = pytest.mark.skip('Need IRAF to run') # Graphics kernel to use. This seems to work on Linux now. PSDEV = 'psdump' EXP2IGNORE = '(NOAO/IRAF ' def without_graphics(func): """Decorator to run a test without turning graphics on""" def wrapper(*args, **kwargs): orig_flag = capable.OF_GRAPHICS try: capable.OF_GRAPHICS = False func(*args, **kwargs) finally: capable.OF_GRAPHICS = orig_flag return wrapper 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), f"New file unfound: {f_new}" assert os.path.exists(f_ref), f"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, f"New file is empty: {f_new}" cmd = f"diff -I '{exp2ig}' {f_ref} {f_new}" assert 0 == os.system(cmd), \ f"Diff of postscript failed! Command = {cmd}" if cleanup: os.remove(f_new) 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 preTstCleanup(): """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 Exception: pass # may belong to another user - don't be chatty 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 is 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(f"Am somehow missing the deletes. Test: {title}") return flistAft[-1] # Either way, throw something raise Exception('Expected single postcript file during: ' f'"{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] @without_graphics def test_dumpspecs(): # get dumpspecs output out_f = io.StringIO() wutil.dumpspecs(outstream=out_f, skip_volatiles=True) out_str = out_f.getvalue() out_f.close() # modify out_str to remove a path that will always be changing out_str = '\n'.join( [l for l in out_str.split('\n') if 'python exec =' not in l]) # modify out_str to handle old versions which printed Tkinter as camel-case # verify it (is version dependent) expected = f"""python ver = {sys.version_info.major}.{sys.version_info.minor} platform = {sys.platform} c.OF_GRAPHICS = False /dev/console owner = <skipped> tkinter use unattempted. """ assert expected.strip() == out_str.strip(), \ f'Unexpected output from wutil.dumpspecs: {out_str}' def test_gkidecode(tmpdir): iraf.plot(_doprint=0) prow_stdout = str(tmpdir.join('test_prow_256.gki')) gki_stdout = str(tmpdir.join('gkidecode_stdout.txt')) gki_stderr = str(tmpdir.join('gkidecode_stderr.txt')) iraf.prow("dev$pix", row=256, StdoutG=prow_stdout) iraf.gkidecode(prow_stdout, Stdout=gki_stdout, Stderr=gki_stderr) assert 'close_workstation' in open(gki_stdout).readlines()[-1] assert not open(gki_stderr).readlines() @pytest.mark.parametrize('test_input', [ 'psdump', 'psi_land', ]) def test_gki_postscript_in_graphcap(test_input): """ Verify that the graphcap file supports psdump and psi_land """ gc = gki.getGraphcap() assert gc, "default graphcap not found" assert test_input in gc, \ f"default graphcap does not support {test_input}" device = gc[test_input] assert device.devname == test_input, \ f"Invalid graphcap device for {test_input}" def test_gki_opcodes(): """ Simple aliveness test for the opcode2name dict """ for opc in gki.GKI_ILLEGAL_LIST: assert gki.opcode2name[opc] == 'gki_unknown' def test_gki_control_codes(): """ Simple aliveness test for the control2name dict """ for ctl in gki.GKI_ILLEGAL_LIST: assert gki.control2name[ctl] == 'control_unknown' @pytest.mark.skipif(is_i386, reason='diff is not identical on i386') def test_gki_single_prow(): """ 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.path.join(DATA_DIR, PSDEV + "_prow_256.ps")) @pytest.mark.skipif(is_i386, reason='diff is not identical on i386') def test_gki_prow_1_append(): """ 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.path.join(DATA_DIR, PSDEV + "_prow_256_250.ps")) @pytest.mark.skipif(is_i386, reason='diff is not identical on i386') def test_gki_prow_2_appends(): """ 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.path.join(DATA_DIR, PSDEV + "_prow_256_250_200.ps")) @pytest.mark.skipif(is_i386, reason='diff is not identical on i386') def test_gki_2_prows_no_append(): """ 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.path.join(DATA_DIR, 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.path.join(DATA_DIR, PSDEV + "_prow_250.ps")) @pytest.mark.skip(reason="Erroneously sends jobs to network printers") def test_gki_prow_to_different_devices(): # rename to disable for now """Test 2 prow calls, each to different devices - one .ps written. 10 May 2012 - rename to disable for now - is sending nightly 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. """ 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) # plot to fake printer, should flush iraf.prow("dev$pix", row=333, dev="lw") # last plot, and should warn @ fake # get output postscript temp file name psOut = getNewTmpPskFile(flistBef, "prow_to_different_devices") # diff diffit(EXP2IGNORE, psOut, os.path.join(DATA_DIR, 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) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_invocation.py����������������������������������������������������������0000644�0001750�0001750�00000011452�14214070451�017431� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys import subprocess import pytest import pyraf from .utils import HAS_IRAF HAS_IPYTHON = True try: import IPython except ImportError: HAS_IPYTHON = False cl_cases = [ (('print(1)'), '1'), (('print(1)'), '1'), (('print(1 + 2)'), '3'), (('print(6 - 1)'), '5'), (('print(14 / 3))'), '4'), (('print(3 * 3)'), '9'), (('unlearn imcoords'), ''), (('bye'), ''), ] if HAS_IRAF: cl_cases.append((('imhead("dev$pix")'), 'dev$pix[512,512][short]: m51 B 600s')) ipython_cases = ( ('print("ipython test")', 'In [1]: ipython test'), ('s = "hello world";s', 'Out[1]: \'hello world\''), ) python_cases = ( ('print("ipython test")', 'ipython test'), ('s = "hello world";s', '\'hello world\''), ) class PyrafEx: def __init__(self): self.code = 0 self.stdout = None self.stderr = None def run(self, args, stdin=None, silent=True, use_ecl=False): """Execute pyraf and store the relevant results """ if isinstance(args, str): args = args.split() cmd = [sys.executable, '-m', 'pyraf', '-x'] if silent: cmd += ['-s'] if use_ecl: cmd += ['-e'] cmd += args proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) if stdin is not None: stdin = stdin.encode('ascii') self.stdout, self.stderr = proc.communicate(stdin) self.stdout = self.stdout.decode('ascii') self.stderr = self.stderr.decode('ascii') self.code = proc.returncode return self @pytest.fixture def _with_pyraf(): return PyrafEx() @pytest.mark.parametrize('test_input', [ ('--version'), ('-V'), ]) def test_invoke_version(_with_pyraf, test_input): """Ensure version reported by command-line options originates in __version__ """ result = _with_pyraf.run(test_input) assert not result.code, result.stderr assert pyraf.__version__ in result.stdout @pytest.mark.parametrize('test_input,expected', cl_cases) @pytest.mark.parametrize('use_ecl', [False, True]) def test_invoke_command(_with_pyraf, test_input, expected, use_ecl): """Issue basic commands to CL parser """ result = _with_pyraf.run(['-c', test_input], use_ecl=use_ecl) assert not result.code, result.stderr assert result.stdout.startswith(expected) @pytest.mark.parametrize('test_input,expected', cl_cases) @pytest.mark.parametrize('use_ecl', [False, True]) def test_invoke_command_direct(_with_pyraf, test_input, expected, use_ecl): """Issue basic commands on pyraf's native shell """ if '/' in test_input: pytest.xfail('Integer division is different as the Python 3 ' 'parser is used here') result = _with_pyraf.run(['-m'], use_ecl=use_ecl, stdin=test_input + '\n.exit') assert not result.code, result.stderr assert result.stdout.strip().endswith(expected) # assert not result.stderr # BUG: Why is there a single newline on stderr? @pytest.mark.parametrize('test_input,expected', python_cases) @pytest.mark.parametrize('use_ecl', [False, True]) def test_invoke_command_no_wrapper_direct(_with_pyraf, test_input, expected, use_ecl): """Issue basic commands on pyraf's passthrough shell """ result = _with_pyraf.run(['-i'], use_ecl=use_ecl, stdin=test_input) _output = result.stdout.strip() begin = _output.find('>>>') output = ''.join( [x.replace('>>>', '').strip() for x in _output[begin:].splitlines()]) assert not result.code, result.stderr assert output == expected @pytest.mark.skipif(not HAS_IPYTHON, reason='IPython must be installed to run') @pytest.mark.parametrize('test_input,expected', ipython_cases) @pytest.mark.parametrize('use_ecl', [False, True]) def test_invoke_command_ipython(_with_pyraf, test_input, expected, use_ecl): """Issue basic commands on pyraf's ipython shell wrapper """ result = _with_pyraf.run('-y', use_ecl=use_ecl, stdin=test_input) assert not result.code, result.stderr assert expected in result.stdout @pytest.mark.parametrize('test_input', [ '--commandwrapper', '--no-commandwrapper', '--ipython', ]) def test_invoke_nosilent(_with_pyraf, test_input): """Ensure full invocation is somehow verbose """ if test_input in ('--ipython', '-y') and not HAS_IPYTHON: pytest.skip('IPython must be installed to run') result = _with_pyraf.run(test_input, silent=False) assert not result.code, result.stderr if HAS_IRAF: assert "Welcome to IRAF." in result.stdout assert "clpackage" in result.stdout assert f"PyRAF {pyraf.__version__}" in result.stdout ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_plot.py����������������������������������������������������������������0000644�0001750�0001750�00000004210�14214070451�016230� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest import os from .utils import HAS_IRAF from pyraf import gki from ..tools import capable if HAS_IRAF: from pyraf import iraf else: pytestmark = pytest.mark.skip('Need IRAF to run') try: import tkinter tkinter.Tk().destroy() except Exception: pytestmark = pytest.mark.skip('No graphics available') markers = [None, 'point', 'box', 'plus', 'cross', 'circle'] graphics = ['tkplot', 'matplotlib'] @pytest.fixture(autouse=True) def setup(): orig_graphics = capable.OF_GRAPHICS capable.OF_GRAPHICS = True iraf.plot() # load plot pkg # gki._resetGraphicsKernel() # clean slate graphenv = os.environ.get('PYRAFGRAPHICS') if graphenv is not None: del os.environ['PYRAFGRAPHICS'] yield gki.kernel.clear() capable.OF_GRAPHICS = orig_graphics if graphenv is not None: os.environ['PYRAFGRAPHICS'] = graphenv @pytest.mark.parametrize('marker', markers) @pytest.mark.parametrize('graphics', graphics) def test_plot_prow(marker, graphics): os.environ['PYRAFGRAPHICS'] = graphics apd = False for row in range(150, 331, 90): if marker is None: iraf.prow('dev$pix', row, wy2=400, append=apd, pointmode=False) else: iraf.prow('dev$pix', row, wy2=400, append=apd, pointmode=True, marker=marker) apd = True @pytest.mark.parametrize('marker', markers) @pytest.mark.parametrize('graphics', graphics) def test_plot_graph(marker, graphics): os.environ['PYRAFGRAPHICS'] = graphics tstr = '' for row in range(150, 331, 90): tstr += 'dev$pix[*,' + str(row) + '],' tstr = tstr[:-1] # rm final comma if marker is None: iraf.graph(tstr, wy2=400, pointmode=False, ltypes='1') else: iraf.graph(tstr, wy2=400, pointmode=True, marker=marker) @pytest.mark.parametrize('graphics', graphics) def test_plot_contour(graphics): os.environ['PYRAFGRAPHICS'] = graphics iraf.contour("dev$pix", Stderr='/dev/null') @pytest.mark.parametrize('graphics', graphics) def test_plot_surface(graphics): os.environ['PYRAFGRAPHICS'] = graphics iraf.surface("dev$pix", Stderr='/dev/null') ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_tasks.py���������������������������������������������������������������0000644�0001750�0001750�00000003211�14214070451�016377� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest from .utils import HAS_IRAF from ..tools.irafglobals import INDEF if HAS_IRAF: from pyraf import iraf else: pytestmark = pytest.mark.skip('Need IRAF to run') @pytest.mark.parametrize('pkg', [ 'clpackage', 'dataio', 'images', 'lists', 'plot', 'system', 'utilities', 'softools', 'noao', 'imcoords', 'imfilter', 'imfit', 'imgeom', 'immatch', 'imutil', 'tv', 'color', 'vol', 'nttools', 'artdata', 'astcat', 'astutil', 'digiphot', 'imred', 'mtlocal', 'obsutil', 'onedspec', 'rv', 'twodspec', ]) def test_load_package(pkg): iraf.load(pkg, doprint=False, hush=False) @pytest.mark.parametrize('task', [ 'imcoords.mkcwcs', 'imcoords.mkcwwcs', 'imgeom.imlintran', 'imgeom.rotate', 'immatch.gregister', 'immatch.imalign', 'immatch.skymap', 'immatch.sregister', 'immatch.wcsmap', 'immatch.wregister', 'proto.ringavg', 'utilities.bases', 'lists.average', 'lists.raverage', 'system.allocate', 'system.deallocate', 'system.devstatus', 'system.diskspace', 'system.devices', 'system.references', 'system.phelp', 'tv.bpmedit', 'softools.mkhelpdb', 'color.rgbdisplay', 'astutil.astradius', ]) def test_load_cl_task(task): t = iraf.getTask(task) t.initTask() @pytest.mark.parametrize('task', ['user.date', 'softools.xc']) def test_exec_foreign_task(task): t = iraf.getTask(task) t(Stdout="/dev/null") @pytest.mark.parametrize('task, pars', [ ('obsutil.specpars', {'xdispersion': INDEF, 'xbin': 1, 'filter': ''}), ]) def test_exec_pset(task, pars): t = iraf.getTask(task) pdict = t.getParDict() for name, value in pars.items(): assert pdict[name].value == value ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647850391.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/test_using_tasks.py���������������������������������������������������������0000644�0001750�0001750�00000025471�14216031627�017624� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import math import os from io import StringIO import pytest from .utils import HAS_STSDAS, HAS_IRAF, DATA_DIR pytestmark = pytest.mark.skipif(not HAS_STSDAS, reason='Need STSDAS to run') if HAS_IRAF: os.environ['PYRAF_NO_DISPLAY'] = '1' from pyraf import iraf # --- Helpers --- def _unlearn_egstp(egstp_obj): """Reset PSET egstp's values """ egstp_obj.unlearn() egstp_obj.lParam() assert egstp_obj.npix == 0, str(egstp_obj.npix) assert egstp_obj.min == 0.0, str(egstp_obj.min) assert egstp_obj.max == 0.0, str(egstp_obj.max) assert egstp_obj.sum == 0.0, str(egstp_obj.sum) def _assertApproxEqual(afloat, bfloat, tolerance=1.0e-12): if math.fabs(bfloat) > tolerance: ratiodiff = math.fabs(1.0 - math.fabs(afloat / (1.0 * bfloat))) assert ratiodiff < tolerance, \ f'{afloat} != {bfloat}, radiodiff = {math.fabs(ratiodiff)}' else: diff = math.fabs(afloat - bfloat) assert diff < tolerance, \ f'{afloat} != {bfloat}, diff = {math.fabs.diff}' def _check_all_dqbits(the_dqbits_obj, valtup): """ Convenience method to check the 16 bitN values of dqbits """ # converts to iraf yes and no's yes_no_map = {True: "iraf.yes", False: "iraf.no"} # check each one for i in range(16): expect_is_true = f'the_dqbits_obj.bit{i+1} == {yes_no_map[bool(valtup[i])]}' result = eval(expect_is_true) msg = f"Expected this to be True: {expect_is_true}" msg = msg.replace('the_dqbits_obj', 'dqbits') assert result, msg # --- Fixtures --- @pytest.fixture def _data(tmpdir): inputs = dict( pset=dict(input1=os.path.join(DATA_DIR, 'pset_msstat_input.fits')), dqbits=dict(input1=str(tmpdir.join('dqbits_im1.fits')), input2=str(tmpdir.join('dqbits_im2.fits')), output=str(tmpdir.join('dqbits_out.fits')))) return inputs @pytest.fixture(scope='function') def _iraf_pset_init(): """Initialize common IRAF tasks for pset tests """ if not HAS_IRAF: return # imports & package loading iraf.stsdas(_doprint=0) iraf.imgtools(_doprint=0) iraf.mstools(_doprint=0) # reset PSET egstp's values _unlearn_egstp(iraf.egstp) @pytest.fixture(scope='function') def _iraf_dqbits_init(_data): """Initialize common IRAF tasks for dqbits tests """ if not HAS_IRAF: return # imports & package loading iraf.stsdas(_doprint=0) iraf.imgtools(_doprint=0) iraf.artdata(_doprint=0) iraf.mstools(_doprint=0) # create two data files as input (dont care if appropriate to mscombine) iraf.imcopy('dev$pix', _data['dqbits']['input1']) iraf.imcopy('dev$pix', _data['dqbits']['input2']) def test_task_min_match(_iraf_pset_init, _data): # Determine whether pyraf can use min-matching to resolve the task # (msstatistics -> msstat) # # Note: # iraf.task does not raise an exception if it fails due to invalid input to # the function. We're scanning stdout/err here as a stop gap measure. stdout, stderr = StringIO(), StringIO() iraf.msst(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.mssta(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.msstat(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.msstati(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.msstatis(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.msstatist(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.msstatisti(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.msstatistic(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) iraf.msstatistics(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) assert "ERROR" not in stdout.getvalue() assert not stderr.getvalue() def test_task_ambiguous_name_raises_exception(_iraf_pset_init, _data): stdout, stderr = StringIO(), StringIO() with pytest.raises(AttributeError): iraf.m(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) with pytest.raises(AttributeError): iraf.ms(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) with pytest.raises(AttributeError): iraf.mss(_data['pset']['input1'], arrays='science', clarray='science', StdoutAppend=stdout, StderrAppend=stderr) def test_pset_msstatistics_science_array(_iraf_pset_init, _data): """Expect decent data """ # Run msstat, which sets egstp values. Test PSET par passing to task # command function as a task top-level par passing msstat.nsstatpar.arrays # and msstat.nsstatpar.clarray, as if they were just msstat pars... # arrays='science' (check "science" arrays only) # clarray='science' (return data to egstp from final "error" array) # So, expect vals from second (final) science array. iraf.msstatistics(_data['pset']['input1'], arrays='science', clarray='science') iraf.egstp.lParam() assert iraf.egstp.npix == 16384, str(iraf.egstp.npix) _assertApproxEqual(iraf.egstp.min, 1141.0) _assertApproxEqual(iraf.egstp.max, 4065.0) _assertApproxEqual(iraf.egstp.sum, 18998468.0) def test_pset_msstatistics_zeroed_error_array(_iraf_pset_init, _data): """Expect zeros """ # Run and get data for retval from error arrays (which are empty) # arrays='science' (check "science" arrays only) # clarray='error' (return data to egstp from final "error" array) # so, since the 'error' arrays are empty (and unchecked), expect all zeroes iraf.msstatistics(_data['pset']['input1'], arrays='science', clarray='error') iraf.egstp.lParam() assert iraf.egstp.npix == 0, str(iraf.egstp.npix) assert iraf.egstp.min == 0.0, str(iraf.egstp.min) assert iraf.egstp.max == 0.0, str(iraf.egstp.max) assert iraf.egstp.sum == 0.0, str(iraf.egstp.sum) def test_pset_msstatistics_191(_iraf_pset_init, _data): """Expect egstp to be properly cleared and used again NOTE: Referenced issue, 191, is no longer available (trac). """ # Regression test to make sure a task can be sent an unadorned PSET # par as a regular function argument (without scope/PSET name given). # This regression-tests #191. # repeat first call to msstat, verify correct results and that we did # not hit anything which exists due to previous calls iraf.msstat(_data['pset']['input1'], arrays='science', clarray='science') iraf.egstp.lParam() assert iraf.egstp.npix == 16384, str(iraf.egstp.npix) _assertApproxEqual(iraf.egstp.min, 1141.0) _assertApproxEqual(iraf.egstp.max, 4065.0) _assertApproxEqual(iraf.egstp.sum, 18998468.0) _unlearn_egstp(iraf.egstp) iraf.msstatistics(_data['pset']['input1'], arrays='science', clarray='error') iraf.egstp.lParam() assert iraf.egstp.npix == 0, str(iraf.egstp.npix) assert iraf.egstp.min == 0.0, str(iraf.egstp.min) assert iraf.egstp.max == 0.0, str(iraf.egstp.max) assert iraf.egstp.sum == 0.0, str(iraf.egstp.sum) _unlearn_egstp(iraf.egstp) iraf.msstat(_data['pset']['input1'], arrays='science', clarray='science') iraf.egstp.lParam() assert iraf.egstp.npix == 16384, str(iraf.egstp.npix) _assertApproxEqual(iraf.egstp.min, 1141.0) _assertApproxEqual(iraf.egstp.max, 4065.0) _assertApproxEqual(iraf.egstp.sum, 18998468.0) def test_pset_msstatistics_save_data(_iraf_pset_init, _data): """Expect a task can save data into a PSET """ # run msstat, which sets egstp values # check PSET egstp's values iraf.msstatistics('dev$pix') iraf.egstp.lParam() assert iraf.egstp.npix == 262144, str(iraf.egstp.npix) assert iraf.egstp.min == -1.0, str(iraf.egstp.min) assert iraf.egstp.max == 19936.0, str(iraf.egstp.max) assert iraf.egstp.sum == 28394234.0, str(iraf.egstp.sum) # reset PSET egstp's values _unlearn_egstp(iraf.egstp) iraf.egstp.lParam() assert iraf.egstp.npix == 0, str(iraf.egstp.npix) assert iraf.egstp.min == 0.0, str(iraf.egstp.min) assert iraf.egstp.max == 0.0, str(iraf.egstp.max) assert iraf.egstp.sum == 0.0, str(iraf.egstp.sum) # run msstat again iraf.msstatistics('dev$pix') # recheck PSET egstp's values iraf.egstp.lParam() assert iraf.egstp.npix == 262144, str(iraf.egstp.npix) assert iraf.egstp.min == -1.0, str(iraf.egstp.min) assert iraf.egstp.max == 19936.0, str(iraf.egstp.max) assert iraf.egstp.sum == 28394234.0, str(iraf.egstp.sum) def test_dqbits_mscombine(_iraf_dqbits_init, _data): """Expect dqbits unaltered after combining data """ # reset PSET dqbits' values iraf.dqbits.unlearn() _check_all_dqbits(iraf.dqbits, (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) iraf.dqbits.lParam() # now set PSET dqbits' values to a non-default set iraf.dqbits.bit2 = iraf.dqbits.bit4 = iraf.dqbits.bit6 = iraf.dqbits.bit8 = iraf.yes _check_all_dqbits(iraf.dqbits, (0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)) iraf.dqbits.lParam() # run mscombine to see what is does with the dqbit pars (shouldn't alter) inputs = ','.join([_data['dqbits']['input1'], _data['dqbits']['input2']]) output = _data['dqbits']['output'] iraf.mscombine(inputs, output) # now, check the PSET - should be unaltered (fixed by #207) iraf.dqbits.lParam() _check_all_dqbits(iraf.dqbits, (0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1648793030.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tests/utils.py��������������������������������������������������������������������0000644�0001750�0001750�00000000744�14221512706�015365� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os import sys from distutils.spawn import find_executable DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') try: from pyraf import iraf iraf.imhead("dev$pix", Stdout=os.devnull, Stderr=os.devnull) except (iraf.IrafError, AttributeError): HAS_IRAF = False HAS_STSDAS = False else: HAS_IRAF = True try: iraf.stsdas(_doprint=0) except (iraf.IrafError, AttributeError): HAS_STSDAS = False else: HAS_STSDAS = True ����������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/textattrib.py���������������������������������������������������������������������0000644�0001750�0001750�00000006016�14212324745�015257� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Implements text rendering using stroked font and OpenGL General description and discussion about the assumptions of how text is to be handled. This will be a phased implementation and initially, some IRAF text features may not be implmented From experiments, these are some properties of IRAF text: 1) Text does not zoom with plot. (affected by gki coordinate transformation only in cl level manipulations. Does not affect this code. 2) Escape sequences for fonts do not appear to match the documentation or code comments. Greek characters do not appear to be supported, nor does bold text. Will eventually support here though 3) Characters do not retain a constant aspect ratio. It appears to change with the aspect ratio of the screen. Either mode can be chosen 4) Characters are fixed width font. Same here, for now (and maybe forever). This implementation will allow some of these properties to be overriden by a system-wide configuration state. See gkiopengl.py """ from . import fontdata CHARPATH_LEFT = 2 CHARPATH_RIGHT = 3 CHARPATH_UP = 4 CHARPATH_DOWN = 5 JUSTIFIED_NORMAL = 0 JUSTIFIED_CENTER = 1 JUSTIFIED_TOP = 6 JUSTIFIED_BOTTOM = 7 JUSTIFIED_LEFT = 2 JUSTIFIED_RIGHT = 3 FONT_ROMAN = 8 FONT_GREEK = 9 FONT_ITALIC = 10 FONT_BOLD = 11 FQUALITY_NORMAL = 0 FQUALITY_LOW = 12 FQUALITY_MEDIUM = 13 FQUALITY_HIGH = 14 class TextAttributes: # Used as a structure definition basically, perhaps it should be made # more sophisticated. def __init__(self): self.charUp = 90. self.charSize = 1. self.charSpace = 0. self.textPath = CHARPATH_RIGHT self.textHorizontalJust = JUSTIFIED_NORMAL self.textVerticalJust = JUSTIFIED_NORMAL self.textFont = FONT_ROMAN self.textQuality = FQUALITY_NORMAL self.textColor = 1 self.font = fontdata.font1 # Place to keep font size and aspect for current window dimensions self.hFontSize = None self.fontAspect = None def set(self, charUp=90., charSize=1., charSpace=0., textPath=CHARPATH_RIGHT, textHorizontalJust=JUSTIFIED_NORMAL, textVerticalJust=JUSTIFIED_NORMAL, textFont=FONT_ROMAN, textQuality=FQUALITY_NORMAL, textColor=1): self.charUp = charUp self.charSize = charSize self.charSpace = charSpace self.textPath = textPath self.textHorizontalJust = textHorizontalJust self.textVerticalJust = textVerticalJust self.textFont = textFont self.textQuality = textQuality self.textColor = textColor # Place to keep font size and aspect for current window dimensions def setFontSize(self, win): """Set the unit font size for a given window using the iraf configuration parameters contained in an attribute class""" conf = win.irafGkiConfig self.hFontSize, self.fontAspect = conf.fontSize(win.gwidget) def getFontSize(self): return self.hFontSize, self.fontAspect ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1646897637.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tkplottext.py���������������������������������������������������������������������0000644�0001750�0001750�00000013640�14212324745�015310� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Implements text rendering using stroked font and Tkplot/X General description and discussion about the assumptions of how text is to be handled. This will be a phased implementation and initially, some IRAF text features may not be implmented From experiments, these are some properties of IRAF text: 1) Text does not zoom with plot. (affected by gki coordinate transformation only in cl level manipulations. Does not affect this code. 2) Escape sequences for fonts do not appear to match the documentation or code comments. Greek characters do not appear to be supported, nor does bold text. Will eventually support here though 3) Characters do not retain a constant aspect ratio. It appears to change with the aspect ratio of the screen. Either mode can be chosen 4) Characters are fixed width font. Same here, for now (and maybe forever). This implementation will allow some of these properties to be overriden by a system-wide configuration state. See gkiopengl.py """ import numpy import math from .textattrib import (CHARPATH_LEFT, CHARPATH_RIGHT, CHARPATH_UP, CHARPATH_DOWN, JUSTIFIED_CENTER, JUSTIFIED_RIGHT, JUSTIFIED_LEFT, JUSTIFIED_NORMAL, JUSTIFIED_TOP, JUSTIFIED_BOTTOM) def softText(win, x, y, textstr): # Generate text using software generated stroked fonts # except for the input x,y, all coordinates are in units of pixels # get unit font size ta = win.textAttributes hsize, fontAspect = ta.getFontSize() vsize = hsize * fontAspect # get current size in unit font units (!) fsize = ta.charSize # We draw the line at fontSizes less than 1/2! Get real. fsize = max(fsize, 0.5) # Character spacing depends on whether text is 'vertical' or 'horizontal' # (relative to character orientation). Figure out what the character # delta offset is in the coordinate system where charUp is the y axis. # First include added space if any hspace = fsize * hsize * (1. + ta.charSpace) vspace = fsize * vsize * (1. + ta.charSpace) if ta.textPath in (CHARPATH_LEFT, CHARPATH_RIGHT): dx = hspace if ta.textPath == CHARPATH_LEFT: dx = -dx dy = 0. else: dx = 0. dy = -vspace if ta.textPath == CHARPATH_UP: dy = -dy # Figure out 'path' size of the text string for use in justification xpath, ypath = (dx * (len(textstr) - 1), dy * (len(textstr) - 1)) charUp = math.fmod(ta.charUp, 360.) if charUp < 0: charUp = charUp + 360. if ta.textPath == CHARPATH_RIGHT: textdir = math.fmod(charUp + 270, 360.) elif ta.textPath == CHARPATH_LEFT: textdir = math.fmod(charUp + 90, 360.) elif ta.textPath == CHARPATH_UP: textdir = charUp elif ta.textPath == CHARPATH_DOWN: textdir = math.fmod(charUp + 180, 360.) # IRAF definition of justification is a bit weird, justification is # for the text string relative to the window. So a rotated string will # be justified relative to the window horizontal and vertical, not the # string's. Thus the need to compute the offsets in window oriented # coordinates. up = 0. < textdir < 180. left = 90. < textdir < 270. deg2rad = math.pi / 180. cosv = math.cos((charUp - 90.) * deg2rad) sinv = math.sin((charUp - 90.) * deg2rad) xpathwin, ypathwin = (cosv * xpath - sinv * ypath, sinv * xpath + cosv * ypath) xcharsize = fsize * max(abs(cosv * hsize + sinv * vsize), abs(cosv * hsize - sinv * vsize)) ycharsize = fsize * max(abs(-sinv * hsize + cosv * vsize), abs(-sinv * hsize - cosv * vsize)) xoffset, yoffset = (0., 0.) xcharoff, ycharoff = (0., 0.) if ta.textHorizontalJust == JUSTIFIED_CENTER: xoffset = -xpathwin / 2. elif ta.textHorizontalJust == JUSTIFIED_RIGHT: if not left: xoffset = -xpathwin xcharoff = -xcharsize / 2. elif ta.textHorizontalJust in (JUSTIFIED_LEFT, JUSTIFIED_NORMAL): if left: xoffset = xpathwin xcharoff = xcharsize / 2. if ta.textVerticalJust == JUSTIFIED_CENTER: yoffset = -ypathwin / 2. elif ta.textVerticalJust == JUSTIFIED_TOP: if up: yoffset = -ypathwin ycharoff = -ycharsize / 2. elif ta.textVerticalJust in (JUSTIFIED_BOTTOM, JUSTIFIED_NORMAL): if not up: yoffset = ypathwin ycharoff = ycharsize / 2. xNetOffset = xoffset + xcharoff yNetOffset = yoffset + ycharoff # note, these offsets presume that the origin of character coordinates # is the center of the character box. This will be taken into account # when drawing the character. # Now start drawing! gw = win.gwidget xwin = float(gw.winfo_width()) ywin = float(gw.winfo_height()) color = win.colorManager.setDrawingColor(ta.textColor) options = {"fill": color} size = fsize * hsize cosrot = numpy.cos((charUp - 90) * numpy.pi / 180) sinrot = numpy.sin((charUp - 90) * numpy.pi / 180) nchar = 0 # The main event! for char in textstr: # draw character with origin at bottom left corner of character box charstrokes = ta.font[ord(char) - ord(' ')] for i in range(len(charstrokes[0])): vertex = numpy.zeros((len(charstrokes[0][i]), 2), numpy.float64) xf = size * charstrokes[0][i] / 27. - fsize * hsize / 2. yf = size * charstrokes[1][ i] * fontAspect / 27. - fsize * vsize / 2. vertex[:, 0]= cosrot*(xf + nchar*dx) \ - sinrot*(yf + nchar*dy) + xNetOffset + xwin*x vertex[:, 1] = ywin - (sinrot * (xf + nchar * dx) + cosrot * (yf + nchar * dy) + yNetOffset + ywin * y) gw.create_line(*(tuple(vertex.ravel().astype(numpy.int32))), **options) nchar = nchar + 1 ������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000033�00000000000�010211� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������27 mtime=1649147960.672998 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/����������������������������������������������������������������������������0000755�0001750�0001750�00000000000�14223000071�013631� 5����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/__init__.py�����������������������������������������������������������������0000644�0001750�0001750�00000000000�14214070451�015741� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/alert.py��������������������������������������������������������������������0000644�0001750�0001750�00000006223�14214070451�015326� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#### # Class AlertDialog # # Purpose # ------- # # AlertDialog's are widgets that allow one to pop up warnings, one line # questions etc. They return a set of standard action numbers being :- # 0 => Cancel was pressed # 1 => Yes was pressed # 2 => No was pressed # # Standard Usage # -------------- # # F = AlertDialog(widget, message) # action = F.Show() #### """ $Id$ """ from .dialog import * class AlertDialog(ModalDialog): def __init__(self, widget, msg): self.widget = widget self.msgString = msg Dialog.__init__(self, widget) def SetupDialog(self): upperFrame = Frame(self.top) upperFrame['relief'] = 'raised' upperFrame['bd'] = 1 upperFrame.pack({'expand':'yes', 'side':'top', 'fill':'both' }) self.bitmap = Label(upperFrame) self.bitmap.pack({'side':'left'}) msgList = self.msgString.split("\n") for i in range(len(msgList)): msgText = Label(upperFrame) msgText["text"] = msgList[i] msgText.pack({'expand':'yes', 'side':'top', 'anchor':'nw', 'fill':'x' }) self.lowerFrame = Frame(self.top) self.lowerFrame['relief'] = 'raised' self.lowerFrame['bd'] = 1 self.lowerFrame.pack({'expand':'yes', 'side':'top', 'pady':'2', 'fill':'both' }) def OkPressed(self): self.TerminateDialog(1) def CancelPressed(self): self.TerminateDialog(0) def NoPressed(self): self.TerminateDialog(2) def CreateButton(self, text, command): self.button = Button(self.lowerFrame) self.button["text"] = text self.button["command"] = command self.button.pack({'expand':'yes', 'pady':'2', 'side':'left'}) #### # Class ErrorDialog # # Purpose # ------- # # To pop up an error message #### class ErrorDialog(AlertDialog): def SetupDialog(self): AlertDialog.SetupDialog(self) self.bitmap['bitmap'] = 'error' self.CreateButton("OK", self.OkPressed) #### # Class WarningDialog # # Purpose # ------- # # To pop up a warning message. #### class WarningDialog(AlertDialog): def SetupDialog(self): AlertDialog.SetupDialog(self) self.bitmap['bitmap'] = 'warning' self.CreateButton("Yes", self.OkPressed) self.CreateButton("No", self.CancelPressed) #### # Class QuestionDialog # # Purpose # ------- # # To pop up a simple question #### class QuestionDialog(AlertDialog): def SetupDialog(self): AlertDialog.SetupDialog(self) self.bitmap['bitmap'] = 'question' self.CreateButton("Yes", self.OkPressed) self.CreateButton("No", self.NoPressed) self.CreateButton("Cancel", self.CancelPressed) #### # Class MessageDialog # # Purpose # ------- # # To pop up a message. #### class MessageDialog(AlertDialog): def SetupDialog(self): AlertDialog.SetupDialog(self) self.bitmap['bitmap'] = 'warning' self.CreateButton("Dismiss", self.CancelPressed) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647588344.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/basicpar.py�����������������������������������������������������������������0000644�0001750�0001750�00000165775�14215031770�016027� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""basicpar.py -- General base class for parameter objects. Broken out from PyRAF's IrafPar class. $Id$ """ import re import sys from . import irafutils, minmatch from .irafglobals import INDEF, Verbose, yes, no, clFloat int_types = (int, ) # container class used for __deepcopy__ method class _EmptyClass: pass # ----------------------------------------------------- # Warning (non-fatal) error. Raise an exception if in # strict mode, or print a message if Verbose is on. # ----------------------------------------------------- # Verbose (set irafglobals.py) determines # whether warning messages are printed when errors are found. The # strict parameter to various methods and functions can be set to # raise an exception on errors; otherwise we do our best to work # around errors, only raising an exception for really serious, # unrecoverable problems. def warning(msg, strict=0, exception=SyntaxError, level=0): if strict: raise exception(msg) elif Verbose>level: sys.stdout.flush() sys.stderr.write('Warning: %s' % msg) if msg[-1:] != '\n': sys.stderr.write('\n') # ----------------------------------------------------- # basic parameter factory # ----------------------------------------------------- _string_types = [ 's', 'f', 'struct', 'z' ] _real_types = [ 'r', 'd' ] def parFactory(fields, strict=0): """parameter factory function fields is a list of the comma-separated fields (as in the .par file). Each entry is a string or None (indicating that field was omitted.) Set the strict parameter to a non-zero value to do stricter parsing (to find errors in the input)""" if len(fields) < 3 or None in fields[0:3]: raise SyntaxError("At least 3 fields must be given") type = fields[1] if type in _string_types: return IrafParS(fields,strict) elif type == 'R': return StrictParR(fields,1) elif type in _real_types: return IrafParR(fields,strict) elif type == "I": return StrictParI(fields,1) elif type == "i": return IrafParI(fields,strict) elif type == "b": return IrafParB(fields,strict) elif type == "ar": return IrafParAR(fields,strict) elif type == "ai": return IrafParAI(fields,strict) elif type == "as": return IrafParAS(fields,strict) elif type == "ab": return IrafParAB(fields,strict) elif type[:1] == "a": raise SyntaxError("Cannot handle arrays of type %s" % type) else: raise SyntaxError("Cannot handle parameter type %s" % type) # -------------------------------------------------------- # Publish the (simple) algorithm for combining scope+name # -------------------------------------------------------- def makeFullName(parScope, parName): """ Create the fully-qualified name (inclues scope if used) """ # Skip scope (and leading dot) if no scope, even in cases where scope # IS used for other pars in the same task. if parScope: return parScope+'.'+parName else: return parName # ----------------------------------------------------- # Set up minmatch dictionaries for parameter fields # ----------------------------------------------------- flist = ("p_name", "p_xtype", "p_type", "p_mode", "p_prompt", "p_scope", "p_value", "p_default", "p_filename", "p_maximum", "p_minimum") _getFieldDict = minmatch.MinMatchDict() for field in flist: _getFieldDict.add(field, field) flist = ("p_prompt", "p_value", "p_filename", "p_maximum", "p_minimum", "p_mode", "p_scope") _setFieldDict = minmatch.MinMatchDict() for field in flist: _setFieldDict.add(field, field) del flist, field # utility function to check whether string is a parameter field def isParField(s): """Returns true if string s appears to be a parameter field""" try: return (s[:2] == "p_") and s in _getFieldDict except minmatch.AmbiguousKeyError: # If ambiguous match, assume it is a parameter field. # An exception will doubtless be raised later, but # there's really no good choice here. return 1 # basic IrafPar attributes # IrafPar's are protected in setattr against adding arbitrary attributes, # and this dictionary is used as a helper in instance initialization _IrafPar_attr_dict = { "name" : None, "type" : None, "mode" : None, "value" : None, "min" : None, "max" : None, "choice" : None, "choiceDict" : None, "prompt" : None, "flags" : 0, "scope" : None, } # flag bits tell whether value has been changed and # whether it was set on the command line. _changedFlag = 1 _cmdlineFlag = 2 # ----------------------------------------------------- # IRAF parameter base class # ----------------------------------------------------- class IrafPar: """Non-array IRAF parameter base class""" def __init__(self,fields,strict=0): orig_len = len(fields) if orig_len < 3 or None in fields[0:3]: raise SyntaxError("At least 3 fields must be given") # # all the attributes that are going to get defined # self.__dict__.update(_IrafPar_attr_dict) self.name = fields[0] self.type = fields[1] self.mode = fields[2] self.scope = None # simple default; may be unused # # put fields into appropriate attributes # while len(fields) < 7: fields.append(None) # self.value = self._coerceValue(fields[3],strict) if fields[4] is not None and '|' in fields[4]: self._setChoice(fields[4].strip(),strict) if fields[5] is not None: if orig_len < 7: warning("Max value illegal when choice list given" + " for parameter " + self.name + " (probably missing comma)", strict) # try to recover by assuming max string is prompt fields[6] = fields[5] fields[5] = None else: warning("Max value illegal when choice list given" + " for parameter " + self.name, strict) else: #XXX should catch ValueError exceptions here and set to null #XXX could also check for missing comma (null prompt, prompt #XXX in max field) if fields[4] is not None: self.min = self._coerceValue(fields[4],strict) if fields[5] is not None: self.max = self._coerceValue(fields[5],strict) if self.min not in [None, INDEF] and \ self.max not in [None, INDEF] and self.max < self.min: warning("Max " + str(self.max) + " is less than minimum " + \ str(self.min) + " for parameter " + self.name, strict) self.min, self.max = self.max, self.min if fields[6] is not None: self.prompt = irafutils.removeEscapes( irafutils.stripQuotes(fields[6])) else: self.prompt = '' # # check attributes to make sure they are appropriate for # this parameter type (e.g. some do not allow choice list # or min/max) # self._checkAttribs(strict) # # check parameter value to see if it is correct # try: self.checkValue(self.value,strict) except ValueError as e: warning("Illegal initial value for parameter\n" + str(e), strict, exception=ValueError) # Set illegal values to None, just like IRAF self.value = None #-------------------------------------------- # public accessor methods #-------------------------------------------- def isLegal(self): """Returns true if current parameter value is legal""" try: # apply a stricter definition of legal here # fixable values have already been fixed # don't accept None values self.checkValue(self.value) return self.value is not None except ValueError: return 0 def setScope(self,value=''): """Set scope value. Written this way to not change the standard set of fields in the comma-separated list. """ # set through dictionary to avoid extra calls to __setattr__ self.__dict__['scope'] = value def setCmdline(self,value=1): """Set cmdline flag""" # set through dictionary to avoid extra calls to __setattr__ if value: self.__dict__['flags'] = self.flags | _cmdlineFlag else: self.__dict__['flags'] = self.flags & ~_cmdlineFlag def isCmdline(self): """Return cmdline flag""" return (self.flags & _cmdlineFlag) == _cmdlineFlag def setChanged(self,value=1): """Set changed flag""" # set through dictionary to avoid another call to __setattr__ if value: self.__dict__['flags'] = self.flags | _changedFlag else: self.__dict__['flags'] = self.flags & ~_changedFlag def isChanged(self): """Return changed flag""" return (self.flags & _changedFlag) == _changedFlag def setFlags(self,value): """Set all flags""" self.__dict__['flags'] = value def isLearned(self, mode=None): """Return true if this parameter is learned Hidden parameters are not learned; automatic parameters inherit behavior from package/cl; other parameters are learned. If mode is set, it determines how automatic parameters behave. If not set, cl.mode parameter determines behavior. """ if "l" in self.mode: return 1 if "h" in self.mode: return 0 if "a" in self.mode: if mode is None: mode = 'ql' # that is, iraf.cl.mode if "h" in mode and "l" not in mode: return 0 return 1 #-------------------------------------------- # other public methods #-------------------------------------------- def getPrompt(self): """Alias for getWithPrompt() for backward compatibility""" return self.getWithPrompt() def getWithPrompt(self): """Interactively prompt for parameter value""" if self.prompt: pstring = self.prompt.split("\n")[0].strip() else: pstring = self.name if self.choice: schoice = list(map(self.toString, self.choice)) pstring = pstring + " (" + "|".join(schoice) + ")" elif self.min not in [None, INDEF] or \ self.max not in [None, INDEF]: pstring = pstring + " (" if self.min not in [None, INDEF]: pstring = pstring + self.toString(self.min) pstring = pstring + ":" if self.max not in [None, INDEF]: pstring = pstring + self.toString(self.max) pstring = pstring + ")" # add current value as default if self.value is not None: pstring = pstring + " (" + self.toString(self.value,quoted=1) + ")" pstring = pstring + ": " # don't redirect stdin/out unless redirected filehandles are also ttys # or unless originals are NOT ttys stdout = sys.__stdout__ try: if sys.stdout.isatty() or not stdout.isatty(): stdout = sys.stdout except AttributeError: pass stdin = sys.__stdin__ try: if sys.stdin.isatty() or not stdin.isatty(): stdin = sys.stdin except AttributeError: pass # print prompt, suppressing both newline and following space stdout.write(pstring) stdout.flush() ovalue = irafutils.tkreadline(stdin) value = ovalue.strip() # loop until we get an acceptable value while (1): try: # null input usually means use current value as default # check it anyway since it might not be acceptable if value == "": value = self._nullPrompt() self.set(value) # None (no value) is not acceptable value after prompt if self.value is not None: return # if not EOF, keep looping if ovalue == "": stdout.flush() raise EOFError("EOF on parameter prompt") print("Error: specify a value for the parameter") except ValueError as e: print(str(e)) stdout.write(pstring) stdout.flush() ovalue = irafutils.tkreadline(stdin) value = ovalue.strip() def get(self, field=None, index=None, lpar=0, prompt=1, native=0, mode="h"): """Return value of this parameter as a string (or in native format if native is non-zero.)""" if field and field != "p_value": # note p_value comes back to this routine, so shortcut that case return self._getField(field,native=native,prompt=prompt) # may prompt for value if prompt flag is set if prompt: self._optionalPrompt(mode) if index is not None: raise SyntaxError("Parameter "+self.name+" is not an array") if native: rv = self.value else: rv = self.toString(self.value) return rv def set(self, value, field=None, index=None, check=1): """Set value of this parameter from a string or other value. Field is optional parameter field (p_prompt, p_minimum, etc.) Index is optional array index (zero-based). Set check=0 to assign the value without checking to see if it is within the min-max range or in the choice list.""" if index is not None: raise SyntaxError("Parameter "+self.name+" is not an array") if field: self._setField(value,field,check=check) else: if check: self.value = self.checkValue(value) else: self.value = self._coerceValue(value) self.setChanged() def checkValue(self,value,strict=0): """Check and convert a parameter value. Raises an exception if the value is not permitted for this parameter. Otherwise returns the value (converted to the right type.) """ v = self._coerceValue(value,strict) return self.checkOneValue(v,strict) def checkOneValue(self,v,strict=0): """Checks a single value to see if it is in range or choice list Allows indirection strings starting with ")". Assumes v has already been converted to right value by _coerceOneValue. Returns value if OK, or raises ValueError if not OK. """ if v in [None, INDEF] or (isinstance(v,str) and v[:1] == ")"): return v elif v == "": # most parameters treat null string as omitted value return None elif self.choice is not None and v not in self.choiceDict: schoice = list(map(self.toString, self.choice)) schoice = "|".join(schoice) raise ValueError("Parameter %s: " "value %s is not in choice list (%s)" % (self.name, str(v), schoice)) elif (self.min not in [None, INDEF] and v<self.min): raise ValueError("Parameter %s: " "value `%s' is less than minimum `%s'" % (self.name, str(v), str(self.min))) elif (self.max not in [None, INDEF] and v>self.max): raise ValueError("Parameter %s: " "value `%s' is greater than maximum `%s'" % (self.name, str(v), str(self.max))) return v def dpar(self, cl=1): """Return dpar-style executable assignment for parameter Default is to write CL version of code; if cl parameter is false, writes Python executable code instead. """ sval = self.toString(self.value, quoted=1) if not cl: if sval == "": sval = "None" s = "%s = %s" % (self.name, sval) return s def fullName(self): """ Return the fully-qualified name (inclues scope if used) """ return makeFullName(self.scope, self.name) # scope can be None or '' def pretty(self,verbose=0): """Return pretty list description of parameter""" # split prompt lines and add blanks in later lines to align them plines = self.prompt.split('\n') for i in range(len(plines)-1): plines[i+1] = 32*' ' + plines[i+1] plines = '\n'.join(plines) namelen = min(len(self.name), 12) pvalue = self.get(prompt=0,lpar=1) alwaysquoted = ['s', 'f', '*gcur', '*imcur', '*ukey', 'pset'] if self.type in alwaysquoted and self.value is not None: pvalue = '"' + pvalue + '"' if self.mode == "h": s = "%13s = %-15s %s" % ("("+self.name[:namelen], pvalue+")", plines) else: s = "%13s = %-15s %s" % (self.name[:namelen], pvalue, plines) if not verbose: return s if self.choice is not None: s = s + "\n" + 32*" " + "|" nline = 33 for i in range(len(self.choice)): sch = str(self.choice[i]) + "|" s = s + sch nline = nline + len(sch) + 1 if nline > 80: s = s + "\n" + 32*" " + "|" nline = 33 elif self.min not in [None, INDEF] or self.max not in [None, INDEF]: s = s + "\n" + 32*" " if self.min not in [None, INDEF]: s = s + str(self.min) + " <= " s = s + self.name if self.max not in [None, INDEF]: s = s + " <= " + str(self.max) return s def save(self, dolist=0): """Return .par format string for this parameter If dolist is set, returns fields as a list of strings. Default is to return a single string appropriate for writing to a file. """ quoted = not dolist fields = 7*[""] fields[0] = self.name fields[1] = self.type fields[2] = self.mode fields[3] = self.toString(self.value,quoted=quoted) if self.choice is not None: schoice = list(map(self.toString, self.choice)) schoice.insert(0,'') schoice.append('') fields[4] = repr('|'.join(schoice)) elif self.min not in [None,INDEF]: fields[4] = self.toString(self.min,quoted=quoted) if self.max not in [None,INDEF]: fields[5] = self.toString(self.max,quoted=quoted) if self.prompt: if quoted: sprompt = repr(self.prompt) else: sprompt = self.prompt # prompt can have embedded newlines (which are printed) sprompt = sprompt.replace(r'\012', '\n') sprompt = sprompt.replace(r'\n', '\n') fields[6] = sprompt # delete trailing null parameters for i in [6,5,4]: if fields[i] != "": break del fields[i] if dolist: return fields else: return ','.join(fields) #-------------------------------------------- # special methods to give desired object syntax #-------------------------------------------- # allow parameter object to be used in arithmetic expression def __coerce__(self, other): return coerce(self.get(native=1), other) # fields are accessible as attributes def __getattr__(self,field): if field[:1] == '_': raise AttributeError(field) try: return self._getField(field, native=1) except SyntaxError as e: if field in _IrafPar_attr_dict: # handle odd-ball case of new code accessing par's new # attr (e.g. scope), with old-code-cached version of par return _IrafPar_attr_dict[field] # return unused default else: raise AttributeError(str(e)) def __setattr__(self,attr,value): # don't allow any new parameters to be added if attr in self.__dict__: self.__dict__[attr] = value elif isParField(attr): #XXX should check=0 be used here? self._setField(value, attr) else: raise AttributeError("No attribute %s for parameter %s" % (attr, self.name)) def __deepcopy__(self, memo): """Deep copy of this parameter object""" new = _EmptyClass() # shallow copy of dictionary suffices for most attributes new.__dict__ = self.__dict__.copy() # value, choice may be lists of atomic items if isinstance(self.value, list): new.value = list(self.value) if isinstance(self.choice, list): new.choice = list(self.choice) # choiceDict is OK with shallow copy because it will # always be reset if choices change new.__class__ = self.__class__ return new def __getstate__(self): """Return state info for pickle""" # choiceDict gets reconstructed if self.choice is None: return self.__dict__ else: d = self.__dict__.copy() d['choiceDict'] = None return d def __setstate__(self, state): """Restore state info from pickle""" self.__dict__.clear() self.__dict__.update(state) if self.choice is not None: self._setChoiceDict() def __str__(self): """Return readable description of parameter""" s = "<" + self.__class__.__name__ + " " + self.name + " " + self.type s = s + " " + self.mode + " " + repr(self.value) if self.choice is not None: schoice = list(map(self.toString, self.choice)) s = s + " |" + "|".join(schoice) + "|" else: s = s + " " + repr(self.min) + " " + repr(self.max) s = s + ' "' + self.prompt + '">' return s #-------------------------------------------- # private methods -- may be used by subclasses, but should # not be needed outside this module #-------------------------------------------- def _checkAttribs(self,strict=0): # by default no restrictions on attributes pass def _setChoice(self,s,strict=0): """Set choice parameter from string s""" clist = _getChoice(s,strict) self.choice = list(map(self._coerceValue, clist)) self._setChoiceDict() def _setChoiceDict(self): """Create dictionary for choice list""" # value is name of choice parameter (same as key) self.choiceDict = {} for c in self.choice: self.choiceDict[c] = c def _nullPrompt(self): """Returns value to use when answer to prompt is null string""" # most parameters just keep current default (even if None) return self.value def _optionalPrompt(self, mode): """Interactively prompt for parameter if necessary Prompt for value if (1) mode is hidden but value is undefined or bad, or (2) mode is query and value was not set on command line Never prompt for "u" mode parameters, which are local variables. """ if (self.mode == "h") or (self.mode == "a" and mode == "h"): # hidden parameter if not self.isLegal(): self.getWithPrompt() elif self.mode == "u": # "u" is a special mode used for local variables in CL scripts # They should never prompt under any circumstances if not self.isLegal(): raise ValueError( "Attempt to access undefined local variable `%s'" % self.name) else: # query parameter if self.isCmdline()==0: self.getWithPrompt() def _getPFilename(self,native,prompt): """Get p_filename field for this parameter Same as get for non-list params """ return self.get(native=native,prompt=prompt) def _getPType(self): """Get underlying datatype for this parameter Just self.type for normal params """ return self.type def _getField(self, field, native=0, prompt=1): """Get a parameter field value""" try: # expand field name using minimum match field = _getFieldDict[field] except KeyError as e: # re-raise the exception with a bit more info raise SyntaxError("Cannot get field " + field + " for parameter " + self.name + "\n" + str(e)) if field == "p_value": # return value of parameter # Note that IRAF returns the filename for list parameters # when p_value is used. I consider this a bug, and it does # not appear to be used by any cl scripts or SPP programs # in either IRAF or STSDAS. It is also in conflict with # the IRAF help documentation. I am making p_value exactly # the same as just a simple CL parameter reference. return self.get(native=native,prompt=prompt) elif field == "p_name": return self.name elif field == "p_xtype": return self.type elif field == "p_type": return self._getPType() elif field == "p_mode": return self.mode elif field == "p_prompt": return self.prompt elif field == "p_scope": return self.scope elif field == "p_default" or field == "p_filename": # these all appear to be equivalent -- they just return the # current PFilename of the parameter (which is the same as the value # for non-list parameters, and is the filename for list parameters) return self._getPFilename(native,prompt) elif field == "p_maximum": if native: return self.max else: return self.toString(self.max) elif field == "p_minimum": if self.choice is not None: if native: return self.choice else: schoice = list(map(self.toString, self.choice)) return "|" + "|".join(schoice) + "|" else: if native: return self.min else: return self.toString(self.min) else: # XXX unimplemented fields: # p_length: maximum string length in bytes -- what to do with it? raise RuntimeError("Program bug in IrafPar._getField()\n" + "Requested field " + field + " for parameter " + self.name) def _setField(self, value, field, check=1): """Set a parameter field value""" try: # expand field name using minimum match field = _setFieldDict[field] except KeyError as e: raise SyntaxError("Cannot set field " + field + " for parameter " + self.name + "\n" + str(e)) if field == "p_prompt": self.prompt = irafutils.removeEscapes(irafutils.stripQuotes(value)) elif field == "p_value": self.set(value,check=check) elif field == "p_filename": # this is only relevant for list parameters (*imcur, *gcur, etc.) self.set(value,check=check) elif field == "p_scope": self.scope = value elif field == "p_maximum": self.max = self._coerceOneValue(value) elif field == "p_minimum": if isinstance(value,str) and '|' in value: self._setChoice(irafutils.stripQuotes(value)) else: self.min = self._coerceOneValue(value) elif field == "p_mode": # not doing any type or value checking here -- setting mode is # rare, so assume that it is being done correctly self.mode = irafutils.stripQuotes(value) else: raise RuntimeError("Program bug in IrafPar._setField()" + "Requested field " + field + " for parameter " + self.name) def _coerceValue(self,value,strict=0): """Coerce parameter to appropriate type Should accept None or null string. """ return self._coerceOneValue(value,strict) def _coerceOneValue(self,value,strict=0): """Coerce a scalar parameter to the appropriate type Default implementation simply prevents direct use of base class. Should accept None or null string. """ raise NotImplementedError("class IrafPar cannot be used directly") # ----------------------------------------------------- # IRAF array parameter base class # ----------------------------------------------------- class IrafArrayPar(IrafPar): """IRAF array parameter class""" def __init__(self,fields,strict=0): orig_len = len(fields) if orig_len < 3: raise SyntaxError("At least 3 fields must be given") # # all the attributes that are going to get defined # self.__dict__.update(_IrafPar_attr_dict) self.name = fields[0] self.type = fields[1] self.mode = fields[2] self.__dict__['shape'] = None # # for array parameters, dimensions follow mode field # and values come from fields after prompt # if len(fields)<4 or fields[3] is None: raise ValueError("Missing dimension field for array parameter") ndim = int(fields[3]) if len(fields) < 4+2*ndim: raise ValueError("Missing array shape fields for array parameter") shape = [] array_size = 1 for i in range(ndim): shape.append(int(fields[4+2*i])) array_size = array_size*shape[-1] self.shape = tuple(shape) nvstart = 7+2*ndim fields.extend([""]*(nvstart-len(fields))) fields.extend([None]*(nvstart+array_size-len(fields))) if len(fields) > nvstart+array_size: raise SyntaxError("Too many values for array" + " for parameter " + self.name) # self.value = [None]*array_size self.value = self._coerceValue(fields[nvstart:],strict) if fields[nvstart-3] is not None and '|' in fields[nvstart-3]: self._setChoice(fields[nvstart-3].strip(),strict) if fields[nvstart-2] is not None: if orig_len < nvstart: warning("Max value illegal when choice list given" + " for parameter " + self.name + " (probably missing comma)", strict) # try to recover by assuming max string is prompt #XXX risky -- all init values might be off by one fields[nvstart-1] = fields[nvstart-2] fields[nvstart-2] = None else: warning("Max value illegal when choice list given" + " for parameter " + self.name, strict) else: self.min = self._coerceOneValue(fields[nvstart-3],strict) self.max = self._coerceOneValue(fields[nvstart-2],strict) if fields[nvstart-1] is not None: self.prompt = irafutils.removeEscapes( irafutils.stripQuotes(fields[nvstart-1])) else: self.prompt = '' if self.min not in [None, INDEF] and \ self.max not in [None, INDEF] and self.max < self.min: warning("Maximum " + str(self.max) + " is less than minimum " + \ str(self.min) + " for parameter " + self.name, strict) self.min, self.max = self.max, self.min # # check attributes to make sure they are appropriate for # this parameter type (e.g. some do not allow choice list # or min/max) # self._checkAttribs(strict) # # check parameter value to see if it is correct # try: self.checkValue(self.value,strict) except ValueError as e: warning("Illegal initial value for parameter\n" + str(e), strict, exception=ValueError) # Set illegal values to None, just like IRAF self.value = None #-------------------------------------------- # public methods #-------------------------------------------- def save(self, dolist=0): """Return .par format string for this parameter If dolist is set, returns fields as a list of strings. Default is to return a single string appropriate for writing to a file. """ quoted = not dolist array_size = 1 for d in self.shape: array_size = d*array_size ndim = len(self.shape) fields = (7+2*ndim+len(self.value))*[""] fields[0] = self.name fields[1] = self.type fields[2] = self.mode fields[3] = str(ndim) next = 4 for d in self.shape: fields[next] = str(d); next += 1 fields[next] = '1'; next += 1 nvstart = 7+2*ndim if self.choice is not None: schoice = list(map(self.toString, self.choice)) schoice.insert(0,'') schoice.append('') fields[nvstart-3] = repr('|'.join(schoice)) elif self.min not in [None,INDEF]: fields[nvstart-3] = self.toString(self.min,quoted=quoted) # insert an escaped line break before min field if quoted: fields[nvstart-3] = '\\\n' + fields[nvstart-3] if self.max not in [None,INDEF]: fields[nvstart-2] = self.toString(self.max,quoted=quoted) if self.prompt: if quoted: sprompt = repr(self.prompt) else: sprompt = self.prompt # prompt can have embedded newlines (which are printed) sprompt = sprompt.replace(r'\012', '\n') sprompt = sprompt.replace(r'\n', '\n') fields[nvstart-1] = sprompt for i in range(len(self.value)): fields[nvstart+i] = self.toString(self.value[i],quoted=quoted) # insert an escaped line break before value fields if dolist: return fields else: fields[nvstart] = '\\\n' + fields[nvstart] return ','.join(fields) def dpar(self, cl=1): """Return dpar-style executable assignment for parameter Default is to write CL version of code; if cl parameter is false, writes Python executable code instead. Note that dpar doesn't even work for arrays in the CL, so we just use Python syntax here. """ sval = list(map(self.toString, self.value, len(self.value)*[1])) for i in range(len(sval)): if sval[i] == "": sval[i] = "None" s = "%s = [%s]" % (self.name, ', '.join(sval)) return s def get(self, field=None, index=None, lpar=0, prompt=1, native=0, mode="h"): """Return value of this parameter as a string (or in native format if native is non-zero.)""" if field: return self._getField(field,native=native,prompt=prompt) # may prompt for value if prompt flag is set #XXX should change _optionalPrompt so we prompt for each element of #XXX the array separately? I think array parameters are #XXX not useful as non-hidden params. if prompt: self._optionalPrompt(mode) if index is not None: sumindex = self._sumindex(index) try: if native: return self.value[sumindex] else: return self.toString(self.value[sumindex]) except IndexError: # should never happen raise SyntaxError("Illegal index [" + repr(sumindex) + "] for array parameter " + self.name) elif native: # return object itself for an array because it is # indexable, can have values assigned, etc. return self else: # return blank-separated string of values for array return str(self) def set(self, value, field=None, index=None, check=1): """Set value of this parameter from a string or other value. Field is optional parameter field (p_prompt, p_minimum, etc.) Index is optional array index (zero-based). Set check=0 to assign the value without checking to see if it is within the min-max range or in the choice list.""" if index is not None: sumindex = self._sumindex(index) try: value = self._coerceOneValue(value) if check: self.value[sumindex] = self.checkOneValue(value) else: self.value[sumindex] = value return except IndexError: # should never happen raise SyntaxError("Illegal index [" + repr(sumindex) + "] for array parameter " + self.name) if field: self._setField(value,field,check=check) else: if check: self.value = self.checkValue(value) else: self.value = self._coerceValue(value) self.setChanged() def checkValue(self,value,strict=0): """Check and convert a parameter value. Raises an exception if the value is not permitted for this parameter. Otherwise returns the value (converted to the right type.) """ v = self._coerceValue(value,strict) for i in range(len(v)): self.checkOneValue(v[i],strict=strict) return v #-------------------------------------------- # special methods #-------------------------------------------- # array parameters can be subscripted # note subscripts start at zero, unlike CL subscripts # that start at one def __getitem__(self, index): return self.get(index=index,native=1) def __setitem__(self, index, value): self.set(value, index=index) def __str__(self): """Return readable description of parameter""" # This differs from non-arrays in that it returns a # print string with just the values. That's because # the object itself is returned as the native value. sv = list(map(str, self.value)) for i in range(len(sv)): if self.value[i] is None: sv[i] = "INDEF" return ' '.join(sv) def __len__(self): return len(self.value) #-------------------------------------------- # private methods #-------------------------------------------- def _sumindex(self, index=None): """Convert tuple index to 1-D index into value""" try: ndim = len(index) except TypeError: # turn index into a 1-tuple index = (index,) ndim = 1 if len(self.shape) != ndim: raise ValueError("Index to %d-dimensional array %s has too %s dimensions" % (len(self.shape), self.name, ["many","few"][len(self.shape) > ndim])) sumindex = 0 for i in range(ndim-1,-1,-1): index1 = index[i] if index1 < 0 or index1 >= self.shape[i]: raise ValueError("Dimension %d index for array %s is out of bounds (value=%d)" % (i+1, self.name, index1)) sumindex = index1 + sumindex*self.shape[i] return sumindex def _getPType(self): """Get underlying datatype for this parameter (strip off 'a' array params)""" return self.type[1:] def _coerceValue(self,value,strict=0): """Coerce parameter to appropriate type Should accept None or null string. Must be an array. """ try: if isinstance(value,str): # allow single blank-separated string as input value = value.split() if len(value) != len(self.value): raise IndexError v = len(self.value)*[0] for i in range(len(v)): v[i] = self._coerceOneValue(value[i],strict) return v except (IndexError, TypeError): raise ValueError("Value must be a " + repr(len(self.value)) + "-element array for " + self.name) def isLegal(self): """Dont call checkValue for arrays""" try: return self.value is not None except ValueError: return 0 # ----------------------------------------------------- # IRAF string parameter mixin class # ----------------------------------------------------- class _StringMixin: """IRAF string parameter mixin class""" #-------------------------------------------- # public methods #-------------------------------------------- def toString(self, value, quoted=0): """Convert a single (non-array) value of the appropriate type for this parameter to a string""" if value is None: return "" elif quoted: return repr(value) else: return value # slightly modified checkOneValue allows minimum match for # choice strings and permits null string as value def checkOneValue(self,v,strict=0): if v is None or v[:1] == ")": return v elif self.choice is not None: try: v = self.choiceDict[v] except minmatch.AmbiguousKeyError: clist = self.choiceDict.getall(v) raise ValueError("Parameter %s: " "ambiguous value `%s', could be %s" % (self.name, str(v), "|".join(clist))) except KeyError: raise ValueError("Parameter %s: " "value `%s' is not in choice list (%s)" % (self.name, str(v), "|".join(self.choice))) elif (self.min is not None and v<self.min): raise ValueError("Parameter %s: " "value `%s' is less than minimum `%s'" % (self.name, str(v), str(self.min))) elif (self.max is not None and v>self.max): raise ValueError("Parameter %s: " "value `%s' is greater than maximum `%s'" % (self.name, str(v), str(self.max))) return v #-------------------------------------------- # private methods #-------------------------------------------- def _checkAttribs(self, strict): """Check initial attributes to make sure they are legal""" if self.min: warning("Minimum value not allowed for string-type parameter " + self.name, strict) self.min = None if self.max: if not self.prompt: warning("Maximum value not allowed for string-type parameter " + self.name + " (probably missing comma)", strict) # try to recover by assuming max string is prompt self.prompt = self.max else: warning("Maximum value not allowed for string-type parameter " + self.name, strict) self.max = None # If not in strict mode, allow file (f) to act just like string (s). # Otherwise choice is also forbidden for file type if strict and self.type == "f" and self.choice: warning("Illegal choice value for type '" + self.type + "' for parameter " + self.name, strict) self.choice = None def _setChoiceDict(self): """Create min-match dictionary for choice list""" # value is full name of choice parameter self.choiceDict = minmatch.MinMatchDict() for c in self.choice: self.choiceDict.add(c, c) def _nullPrompt(self): """Returns value to use when answer to prompt is null string""" # for string, null string is a legal value # keep current default unless it is None if self.value is None: return "" else: return self.value def _coerceOneValue(self,value,strict=0): if value is None: return value elif isinstance(value,str): # strip double quotes and remove escapes before quotes return irafutils.removeEscapes(irafutils.stripQuotes(value)) else: return str(value) # ----------------------------------------------------- # IRAF string parameter class # ----------------------------------------------------- class IrafParS(_StringMixin, IrafPar): """IRAF string parameter class""" pass # ----------------------------------------------------- # IRAF string array parameter class # ----------------------------------------------------- class IrafParAS(_StringMixin,IrafArrayPar): """IRAF string array parameter class""" pass # ----------------------------------------------------- # IRAF boolean parameter mixin class # ----------------------------------------------------- class _BooleanMixin: """IRAF boolean parameter mixin class""" #-------------------------------------------- # public methods #-------------------------------------------- def toString(self, value, quoted=0): if value in [None, INDEF]: return "" elif isinstance(value,str): # presumably an indirection value ')task.name' if quoted: return repr(value) else: return value else: # must be internal yes, no value return str(value) #-------------------------------------------- # private methods #-------------------------------------------- def _checkAttribs(self, strict): """Check initial attributes to make sure they are legal""" if self.min: warning("Minimum value not allowed for boolean-type parameter " + self.name, strict) self.min = None if self.max: if not self.prompt: warning("Maximum value not allowed for boolean-type parameter " + self.name + " (probably missing comma)", strict) # try to recover by assuming max string is prompt self.prompt = self.max else: warning("Maximum value not allowed for boolean-type parameter " + self.name, strict) self.max = None if self.choice: warning("Choice values not allowed for boolean-type parameter " + self.name, strict) self.choice = None # accepts special yes, no objects, integer values 0,1 or # string 'yes','no' and variants # internal value is yes, no, None/INDEF, or indirection string def _coerceOneValue(self,value,strict=0): if value == INDEF: return INDEF elif value is None or value == "": return None elif value in (1, 1.0, yes, "yes", "YES", "y", "Y", True): return yes elif value in (0, 0.0, no, "no", "NO", "n", "N", False): return no elif isinstance(value,str): v2 = irafutils.stripQuotes(value.strip()) if v2 == "" or v2 == "INDEF" or \ ((not strict) and (v2.upper() == "INDEF")): return INDEF elif v2[0:1] == ")": # assume this is indirection -- just save it as a string return v2 raise ValueError("Parameter %s: illegal boolean value %s or type %s" % (self.name, repr(value), str(type(value)))) # ----------------------------------------------------- # IRAF boolean parameter class # ----------------------------------------------------- class IrafParB(_BooleanMixin,IrafPar): """IRAF boolean parameter class""" pass # ----------------------------------------------------- # IRAF boolean array parameter class # ----------------------------------------------------- class IrafParAB(_BooleanMixin,IrafArrayPar): """IRAF boolean array parameter class""" pass # ----------------------------------------------------- # IRAF integer parameter mixin class # ----------------------------------------------------- class _IntMixin: """IRAF integer parameter mixin class""" #-------------------------------------------- # public methods #-------------------------------------------- def toString(self, value, quoted=0): if value is None: return "" else: return str(value) #-------------------------------------------- # private methods #-------------------------------------------- # coerce value to integer def _coerceOneValue(self,value,strict=0): if value == INDEF: return INDEF elif value is None or isinstance(value,int): return value elif value in ("", "None", "NONE"): return None elif isinstance(value,float): # try converting to integer try: return int(value) except (ValueError, OverflowError): pass elif isinstance(value,str): s2 = irafutils.stripQuotes(value.strip()) if s2 == "INDEF" or \ ((not strict) and (s2.upper() == "INDEF")): return INDEF elif s2[0:1] == ")": # assume this is indirection -- just save it as a string return s2 elif s2[-1:] == "x": # hexadecimal return int(s2[:-1],16) elif "." in s2: # try interpreting as a float and converting to integer try: return int(float(s2)) except (ValueError, OverflowError): pass else: try: return int(s2) except ValueError: pass else: # maybe it has an int method try: return int(value) except ValueError: pass raise ValueError("Parameter %s: illegal integer value %s" % (self.name, repr(value))) # ----------------------------------------------------- # IRAF integer parameter class # ----------------------------------------------------- class IrafParI(_IntMixin,IrafPar): """IRAF integer parameter class""" pass # ----------------------------------------------------- # IRAF integer array parameter class # ----------------------------------------------------- class IrafParAI(_IntMixin,IrafArrayPar): """IRAF integer array parameter class""" pass # ----------------------------------------------------- # Strict integer parameter mixin class # ----------------------------------------------------- class _StrictIntMixin(_IntMixin): """Strict integer parameter mixin class""" #-------------------------------------------- # public methods #-------------------------------------------- def toString(self, value, quoted=0): return str(value) #-------------------------------------------- # private methods #-------------------------------------------- # coerce value to integer def _coerceOneValue(self,value,strict=0): if value is None or isinstance(value,int): return value elif isinstance(value,str): s2 = irafutils.stripQuotes(value.strip()) if s2[-1:] == "x": # hexadecimal return int(s2[:-1],16) elif s2 == '': raise ValueError('Parameter '+self.name+ \ ': illegal empty integer value') else: # see if it is a stringified int try: return int(s2) except ValueError: pass # otherwise it is not a strict integer raise ValueError("Parameter %s: illegal integer value %s" % (self.name, repr(value))) # ----------------------------------------------------- # Strict integer parameter class # ----------------------------------------------------- class StrictParI(_StrictIntMixin,IrafPar): """Strict integer parameter class""" pass # ----------------------------------------------------- # IRAF real parameter mixin class # ----------------------------------------------------- _re_d = re.compile(r'[Dd]') _re_colon = re.compile(r':') class _RealMixin: """IRAF real parameter mixin class""" #-------------------------------------------- # public methods #-------------------------------------------- def toString(self, value, quoted=0): if value is None: return "" else: return str(value) #-------------------------------------------- # private methods #-------------------------------------------- def _checkAttribs(self, strict): """Check initial attributes to make sure they are legal""" if self.choice: warning("Choice values not allowed for real-type parameter " + self.name, strict) self.choice = None # coerce value to real # using clFloat in Py3 reproduces same numerical values as on Py2 def _coerceOneValue(self,value,strict=0): if value == INDEF: return INDEF elif value is None or isinstance(value,clFloat): return value elif isinstance(value,float): return clFloat(value) elif value in ("", "None", "NONE"): return None elif isinstance(value, int_types): return clFloat(value) elif isinstance(value,str): s2 = irafutils.stripQuotes(value.strip()) if s2 == "INDEF" or \ ((not strict) and (s2.upper() == "INDEF")): return INDEF elif s2[0:1] == ")": # assume this is indirection -- just save it as a string return s2 # allow +dd:mm:ss.s sexagesimal format for floats fvalue = 0.0 vscale = 1.0 vsign = 1 i1 = 0 mm = _re_colon.search(s2) if mm is not None: if s2[0:1] == "-": i1 = 1 vsign = -1 elif s2[0:1] == "+": i1 = 1 while mm is not None: i2 = mm.start() fvalue = fvalue + int(s2[i1:i2])/vscale i1 = i2+1 vscale = vscale*60.0 mm = _re_colon.search(s2,i1) # special handling for d exponential notation mm = _re_d.search(s2,i1) try: if mm is None: return clFloat(vsign*(fvalue + float(s2[i1:])/vscale)) else: return clFloat(vsign*(fvalue + \ float(s2[i1:mm.start()]+"E"+s2[mm.end():])/vscale)) except ValueError: pass else: # maybe it has a float method try: return clFloat(value) except ValueError: pass raise ValueError("Parameter %s: illegal float value %s" % (self.name, repr(value))) # ----------------------------------------------------- # IRAF real parameter class # ----------------------------------------------------- class IrafParR(_RealMixin,IrafPar): """IRAF real parameter class""" pass # ----------------------------------------------------- # IRAF real array parameter class # ----------------------------------------------------- class IrafParAR(_RealMixin,IrafArrayPar): """IRAF real array parameter class""" pass # ----------------------------------------------------- # Strict real parameter mixin class # ----------------------------------------------------- class _StrictRealMixin(_RealMixin): """Strict real parameter mixin class""" #-------------------------------------------- # public methods #-------------------------------------------- def toString(self, value, quoted=0): return str(value) #-------------------------------------------- # private methods #-------------------------------------------- # coerce value to real def _coerceOneValue(self,value,strict=0): if value is None or isinstance(value,clFloat): return value elif isinstance(value,float): return clFloat(value) elif isinstance(value, int_types): return clFloat(value) elif isinstance(value,str): s2 = irafutils.stripQuotes(value.strip()) if s2 == '': raise ValueError('Parameter '+self.name+ \ ': illegal empty float value') # allow +dd:mm:ss.s sexagesimal format for floats fvalue = 0.0 vscale = 1.0 vsign = 1 i1 = 0 mm = _re_colon.search(s2) if mm is not None: if s2[0:1] == "-": i1 = 1 vsign = -1 elif s2[0:1] == "+": i1 = 1 while mm is not None: i2 = mm.start() fvalue = fvalue + int(s2[i1:i2])/vscale i1 = i2+1 vscale = vscale*60.0 mm = _re_colon.search(s2,i1) # special handling for d exponential notation mm = _re_d.search(s2,i1) try: if mm is None: return clFloat(vsign*(fvalue + float(s2[i1:])/vscale)) else: return clFloat(vsign*(fvalue + \ float(s2[i1:mm.start()]+"E"+s2[mm.end():])/vscale)) except ValueError: pass # see if it's a stringified float try: return clFloat(s2) except ValueError: raise ValueError("Parameter %s: illegal float value %s" % (self.name, repr(value))) # Otherwise it is not a strict float raise ValueError("Parameter %s: illegal float value %s" % (self.name, repr(value))) # ----------------------------------------------------- # Strict real parameter class # ----------------------------------------------------- class StrictParR(_StrictRealMixin,IrafPar): """Strict real parameter class""" pass # ----------------------------------------------------- # Utility routine for parsing choice string # ----------------------------------------------------- _re_choice = re.compile(r'\|') def _getChoice(s, strict): clist = s.split("|") # string is allowed to start and end with "|", so ignore initial # and final empty strings if not clist[0]: del clist[0] if len(clist)>1 and not clist[-1]: del clist[-1] return clist ���././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/capable.py������������������������������������������������������������������0000644�0001750�0001750�00000015245�14214070451�015612� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Learn basic capabilities here (e.g. can we display graphics?). This is meant to be fast and light, having no complicated dependencies, so that any module can fearlessly import this without adverse affects or performance concerns. $Id$ """ import os import sys descrip = "basic capabilities file, last edited: 28 Dec 2017" def is_darwin_and_x(): """ Convenience function. Returns True if is an X11-linked Python/tkinter build on OSX. This is intended to be quick and easy without further imports. As a result, this relies on the assumption that on OSX, PyObjC is installed (only) in the Framework builds of Python. """ if not sys.platform == 'darwin': return False return which_darwin_linkage() == "x11" def which_darwin_linkage(force_otool_check=False): """ Convenience function. Returns one of ('x11', 'aqua') in answer to the question of whether this is an X11-linked Python/tkinter, or a natively built (framework, Aqua) one. This is only for OSX. This relies on the assumption that on OSX, PyObjC is installed in the Framework builds of Python. If it doesn't find PyObjC, this inspects the actual tkinter library binary via otool. One driving requirement here is to try to make the determination quickly and quietly without actually importing/loading any GUI libraries. We even want to avoid importing tkinter if we can. """ # sanity check if sys.platform != 'darwin': raise OSError('Incorrect usage, not on OSX') # If not forced to run otool, then make some quick and dirty # simple checks/assumptions, which do not add to startup time and do not # attempt to initialize any graphics. if not force_otool_check: # There will (for now) only ever be an aqua-linked Python/tkinter # when using Ureka on darwin, so this is an easy short-circuit check. if 'UR_DIR' in os.environ: return "aqua" # There will *usually* be PyObjC modules on sys.path on the natively- # linked Python. This is assumed to be always correct on Python 2.x, as # of 2012. This is kludgy but quick and effective. sp = ",".join(sys.path) sp = sp.lower().strip(',') if '/pyobjc' in sp or 'pyobjc,' in sp or 'pyobjc/' in sp or sp.endswith('pyobjc'): return "aqua" # Try one more thing - look for the physical PyObjC install dir under site-packages # The assumption above using sys.path does not seem to be correct as of the # combination of Python2.7.9/PyObjC3.0.4/2015. sitepacksloc = os.path.split(os.__file__)[0]+'/site-packages/objc' if os.path.exists(sitepacksloc): return "aqua" # OK, no trace of PyObjC found - need to fall through to the forced otool check. # Use otool shell command import tkinter as TKNTR import subprocess # nosec try: tk_dyn_lib = TKNTR._tkinter.__file__ except AttributeError: # happens on Ureka if 'UR_DIR' in os.environ: return 'aqua' else: return 'unknown' libs = subprocess.check_output(('/usr/bin/otool', '-L', tk_dyn_lib)).decode('ascii') # nosec if libs.find('/libX11.') >= 0: return "x11" else: return "aqua" def get_dc_owner(raises, mask_if_self): """ Convenience function to return owner of /dev/console. If raises is True, this raises an exception on any error. If not, it returns any error string as the owner name. If owner is self, and if mask_if_self, returns "<self>".""" try: from pwd import getpwuid owner_uid = os.stat('/dev/console').st_uid self_uid = os.getuid() if mask_if_self and owner_uid == self_uid: return "<self>" owner_name = getpwuid(owner_uid).pw_name return owner_name except Exception as e: if raises: raise e else: return str(e) OF_GRAPHICS = True if 'PYRAF_NO_DISPLAY' in os.environ or 'PYTOOLS_NO_DISPLAY' in os.environ: OF_GRAPHICS = False if OF_GRAPHICS and sys.platform == 'darwin': # # On OSX, there is an AppKit error where Python itself will abort if # tkinter operations (e.g. tkinter._test() ...) are attempted when running # from a remote terminal. In these situations, it is not even safe to put # the code in a try/except block, since the AppKit error seems to happen # *asynchronously* within ObjectiveC code. See PyRAF ticket #149. # # SO, let's try a quick simple test here (only on OSX) to find out if we # are the "console user". If we are not, then we don't even want to attempt # any windows/graphics calls. See "console user" here: # http://developer.apple.com/library/mac/#technotes/tn2083/_index.html # If we are the console user, we own /dev/console and can read from it. # When no one is logged in, /dev/console is owned by "root". When user "bob" # is logged in locally/physically, /dev/console is owned by "bob". # However, if "bob" restarts the X server while logged in, /dev/console # may be owned by "sysadmin" - so we check for that. # if 'PYRAF_YES_DISPLAY' not in os.environ: # the use of PYRAF_YES_DISPLAY is a temporary override while we # debug why a user might have no read-acces to /dev/console dc_owner = get_dc_owner(False, False) OF_GRAPHICS = dc_owner == 'sysadmin' or os.access("/dev/console", os.R_OK) # Add a double-check for remote X11 users. We *think* this is a smaller # set of cases, so we do it last minute here: if not OF_GRAPHICS: # On OSX, but logged in remotely. Normally (with native build) this # means there are no graphics. But, what if they're calling an # X11-linked Python? Then we should allow graphics to be attempted. OF_GRAPHICS = is_darwin_and_x() # OF_GRAPHICS will be True here in only two cases (2nd should be rare): # An OSX Python build linked with X11, or # An OSX Python build linked natively where PyObjC was left out # After all that, we may have decided that we want graphics. Now # that we know it is ok to try to import tkinter, we can test if it # is there. If it is not, we are not capable of graphics. if OF_GRAPHICS : try : import tkinter as TKNTR except ImportError : TKINTER_IMPORT_FAILED = 1 OF_GRAPHICS = False # Using tkFileDialog from PyRAF (and maybe in straight TEAL) is crashing python # itself on OSX only. Allow on Linux. Mac: use this until PyRAF #171 fixed. OF_TKFD_IN_EPAR = True if sys.platform == 'darwin' and OF_GRAPHICS and \ not is_darwin_and_x(): # if framework ver OF_TKFD_IN_EPAR = 'TEAL_TRY_TKFD' in list(os.environ.keys()) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/cfgpars.py������������������������������������������������������������������0000644�0001750�0001750�00000165757�14214070451�015666� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Contains the ConfigObjPars class and any related functionality. $Id$ """ import configobj import copy import glob import os import stat import sys import validate # Local modules from . import basicpar, eparoption, irafutils, taskpars, vtor_checks # Globals and useful functions APP_NAME = 'TEAL' TASK_NAME_KEY = '_task_name_' class DuplicateKeyError(Exception): pass class NoCfgFileError(Exception): pass def getAppDir(): """ Return our application dir. Create it if it doesn't exist. """ # Be sure the resource dir exists theDir = os.path.expanduser('~/.')+APP_NAME.lower() if not os.path.exists(theDir): try: os.mkdir(theDir) except OSError: print('Could not create "'+theDir+'" to save GUI settings.') theDir = "./"+APP_NAME.lower() return theDir def getObjectFromTaskArg(theTask, strict, setAllToDefaults): """ Take the arg (usually called theTask), which can be either a subclass of ConfigObjPars, or a string package name, or a .cfg filename - no matter what it is - take it and return a ConfigObjPars object. strict - bool - warning severity, passed to the ConfigObjPars() ctor setAllToDefaults - bool - if theTask is a pkg name, force all to defaults """ # Already in the form we need (instance of us or of subclass) if isinstance(theTask, ConfigObjPars): if setAllToDefaults: raise RuntimeError('Called getObjectFromTaskArg with existing'+\ ' object AND setAllToDefaults - is unexpected use case.') # If it is an existing object, make sure it's internal param list is # up to date with it's ConfigObj dict, since the user may have manually # edited the dict before calling us. theTask.syncParamList(False) # use strict somehow? # Note - some validation is done here in IrafPar creation, but it is # not the same validation done by the ConfigObj s/w (no check funcs). # Do we want to do that too here? return theTask # For example, a .cfg file if os.path.isfile(str(theTask)): try: return ConfigObjPars(theTask, strict=strict, setAllToDefaults=setAllToDefaults) except KeyError: # this might just be caused by a file sitting in the local cwd with # the same exact name as the package we want to import, let's see if theTask.find('.') > 0: # it has an extension, like '.cfg' raise # this really was an error # else we drop down to the next step - try it as a pkg name # Else it must be a Python package name to load if isinstance(theTask, str) and setAllToDefaults: # NOTE how we pass the task name string in setAllToDefaults return ConfigObjPars('', setAllToDefaults=theTask, strict=strict) else: return getParsObjForPyPkg(theTask, strict) def getEmbeddedKeyVal(cfgFileName, kwdName, dflt=None): """ Read a config file and pull out the value of a given keyword. """ # Assume this is a ConfigObj file. Use that s/w to quickly read it and # put it in dict format. Assume kwd is at top level (not in a section). # The input may also be a .cfgspc file. # # Only use ConfigObj here as a tool to generate a dict from a file - do # not use the returned object as a ConfigObj per se. As such, we can call # with "simple" format, ie. no cfgspc, no val'n, and "list_values"=False. try: junkObj = configobj.ConfigObj(cfgFileName, list_values=False) except: if kwdName == TASK_NAME_KEY: raise KeyError('Can not parse as a parameter config file: '+ \ '\n\t'+os.path.realpath(cfgFileName)) else: raise KeyError('Unfound key "'+kwdName+'" while parsing: '+ \ '\n\t'+os.path.realpath(cfgFileName)) if kwdName in junkObj: retval = junkObj[kwdName] del junkObj return retval # Not found if dflt is not None: del junkObj return dflt else: if kwdName == TASK_NAME_KEY: raise KeyError('Can not parse as a parameter config file: '+ \ '\n\t'+os.path.realpath(cfgFileName)) else: raise KeyError('Unfound key "'+kwdName+'" while parsing: '+ \ '\n\t'+os.path.realpath(cfgFileName)) def findCfgFileForPkg(pkgName, theExt, pkgObj=None, taskName=None): """ Locate the configuration files for/from/within a given python package. pkgName is a string python package name. This is used unless pkgObj is given, in which case pkgName is taken from pkgObj.__name__. theExt is either '.cfg' or '.cfgspc'. If the task name is known, it is given as taskName, otherwise one is determined using the pkgName. Returns a tuple of (package-object, cfg-file-name). """ # arg check ext = theExt if ext[0] != '.': ext = '.'+theExt # Do the import, if needed pkgsToTry = {} if pkgObj: pkgsToTry[pkgObj.__name__] = pkgObj else: # First try something simple like a regular or dotted import try: fl = [] if pkgName.find('.') > 0: fl = [ pkgName[:pkgName.rfind('.')], ] pkgsToTry[str(pkgName)] = __import__(str(pkgName), fromlist=fl) except: throwIt = True # One last case to try is something like "csc_kill" from # "acstools.csc_kill", but this convenience capability will only be # allowed if the parent pkg (acstools) has already been imported. if isinstance(pkgName, str) and pkgName.find('.') < 0: matches = [x for x in sys.modules.keys() if x.endswith("."+pkgName)] if len(matches)>0: throwIt = False for mmm in matches: pkgsToTry[mmm] = sys.modules[mmm] if throwIt: raise NoCfgFileError("Unfound package or "+ext+" file via: "+\ "import "+str(pkgName)) # Now that we have the package object (or a few of them to try), for each # one find the .cfg or .cfgspc file, and return # Return as soon as ANY match is found. for aPkgName in pkgsToTry: aPkg = pkgsToTry[aPkgName] path = os.path.dirname(aPkg.__file__) if len(path) < 1: path = '.' flist = irafutils.rglob(path, "*"+ext) if len(flist) < 1: continue # Go through these and find the first one for the assumed or given task # name. The task name for 'BigBlackBox.drizzle' would be 'drizzle'. if taskName is None: taskName = aPkgName.split(".")[-1] flist.sort() for f in flist: # A .cfg file gets checked for _task_name_=val, but a .cfgspc file # will have a string check function signature as the val. if ext == '.cfg': itsTask = getEmbeddedKeyVal(f, TASK_NAME_KEY, '') else: # .cfgspc sigStr = getEmbeddedKeyVal(f, TASK_NAME_KEY, '') # .cfgspc file MUST have an entry for TASK_NAME_KEY w/ a default itsTask = vtor_checks.sigStrToKwArgsDict(sigStr)['default'] if itsTask == taskName: # We've found the correct file in an installation area. Return # the package object and the found file. return aPkg, f # What, are you still here? raise NoCfgFileError('No valid '+ext+' files found in package: "'+ \ str(pkgName)+'" for task: "'+str(taskName)+'"') def findAllCfgTasksUnderDir(aDir): """ Finds all installed tasks by examining any .cfg files found on disk at and under the given directory, as an installation might be. This returns a dict of { file name : task name } """ retval = {} for f in irafutils.rglob(aDir, '*.cfg'): retval[f] = getEmbeddedKeyVal(f, TASK_NAME_KEY, '') return retval def getCfgFilesInDirForTask(aDir, aTask, recurse=False): """ This is a specialized function which is meant only to keep the same code from needlessly being much repeated throughout this application. This must be kept as fast and as light as possible. This checks a given directory for .cfg files matching a given task. If recurse is True, it will check subdirectories. If aTask is None, it returns all files and ignores aTask. """ if recurse: flist = irafutils.rglob(aDir, '*.cfg') else: flist = glob.glob(aDir+os.sep+'*.cfg') if aTask: retval = [] for f in flist: try: if aTask == getEmbeddedKeyVal(f, TASK_NAME_KEY, ''): retval.append(f) except Exception as e: print('Warning: '+str(e)) return retval else: return flist def getParsObjForPyPkg(pkgName, strict): """ Locate the appropriate ConfigObjPars (or subclass) within the given package. NOTE this begins the same way as getUsrCfgFilesForPyPkg(). Look for .cfg file matches in these places, in this order: 1 - any named .cfg file in current directory matching given task 2 - if there exists a ~/.teal/<taskname>.cfg file 3 - any named .cfg file in SOME*ENV*VAR directory matching given task 4 - the installed default .cfg file (with the given package) """ # Get the python package and it's .cfg file - need this no matter what installedPkg, installedFile = findCfgFileForPkg(pkgName, '.cfg') theFile = None tname = getEmbeddedKeyVal(installedFile, TASK_NAME_KEY) # See if the user has any of their own .cfg files in the cwd for this task if theFile is None: flist = getCfgFilesInDirForTask(os.getcwd(), tname) if len(flist) > 0: if len(flist) == 1: # can skip file times sort theFile = flist[0] else: # There are a few different choices. In the absence of # requirements to the contrary, just take the latest. Set up a # list of tuples of (mtime, fname) so we can sort by mtime. ftups = [ (os.stat(f)[stat.ST_MTIME], f) for f in flist] ftups.sort() theFile = ftups[-1][1] # See if the user has any of their own app-dir .cfg files for this task if theFile is None: flist = getCfgFilesInDirForTask(getAppDir(), tname) # verifies tname flist = [f for f in flist if os.path.basename(f) == tname+'.cfg'] if len(flist) > 0: theFile = flist[0] if len(flist) != 1: # should never happen raise ValueError(str(flist)) # Add code to check an env. var defined area? (speak to users first) # Did we find one yet? If not, use the installed version useInstVer = False if theFile is None: theFile = installedFile useInstVer = True # Create a stand-in instance from this file. Force a read-only situation # if we are dealing with the installed, (expected to be) unwritable file. return ConfigObjPars(theFile, associatedPkg=installedPkg, forceReadOnly=useInstVer, strict=strict) def getUsrCfgFilesForPyPkg(pkgName): """ See if the user has one of their own local .cfg files for this task, such as might be created automatically during the save of a read-only package, and return their names. """ # Get the python package and it's .cfg file thePkg, theFile = findCfgFileForPkg(pkgName, '.cfg') # See if the user has any of their own local .cfg files for this task tname = getEmbeddedKeyVal(theFile, TASK_NAME_KEY) flist = getCfgFilesInDirForTask(getAppDir(), tname) return flist def checkSetReadOnly(fname, raiseOnErr = False): """ See if we have write-privileges to this file. If we do, and we are not supposed to, then fix that case. """ if os.access(fname, os.W_OK): # We can write to this but it is supposed to be read-only. Fix it. # Take away usr-write, leave group and other alone, though it # may be simpler to just force/set it to: r--r--r-- or r-------- irafutils.setWritePrivs(fname, False, ignoreErrors= not raiseOnErr) def flattenDictTree(aDict): """ Takes a dict of vals and dicts (so, a tree) as input, and returns a flat dict (only one level) as output. All key-vals are moved to the top level. Sub-section dict names (keys) are ignored/dropped. If there are name collisions, an error is raised. """ retval = {} for k in aDict: val = aDict[k] if isinstance(val, dict): # This val is a dict, get its data (recursively) into a flat dict subDict = flattenDictTree(val) # Merge its dict of data into ours, watching for NO collisions rvKeySet = set(retval.keys()) sdKeySet = set(subDict.keys()) intr = rvKeySet.intersection(sdKeySet) if len(intr) > 0: raise DuplicateKeyError("Flattened dict already has "+ \ "key(s): "+str(list(intr))+" - cannot flatten this.") else: retval.update(subDict) else: if k in retval: raise DuplicateKeyError("Flattened dict already has key: "+\ k+" - cannot flatten this.") else: retval[k] = val return retval def countKey(theDict, name): """ Return the number of times the given par exists in this dict-tree, since the same key name may be used in different sections/sub-sections. """ retval = 0 for key in theDict: val = theDict[key] if isinstance(val, dict): retval += countKey(val, name) # recurse else: if key == name: retval += 1 # can't break, even tho we found a hit, other items on # this level will not be named "name", but child dicts # may have further counts return retval def findFirstPar(theDict, name, _depth=0): """ Find the given par. Return tuple: (its own (sub-)dict, its value). Returns the first match found, without checking whether the given key name is unique or whether it is used in multiple sections. """ for key in theDict: val = theDict[key] # print _depth*' ', key, str(val)[:40] if isinstance(val, dict): retval = findFirstPar(val, name, _depth=_depth+1) # recurse if retval is not None: return retval # else keep looking else: if key == name: return theDict, theDict[name] # else keep looking # if we get here then we have searched this whole (sub)-section and its # descendants, and found no matches. only raise if we are at the top. if _depth == 0: raise KeyError(name) else: return None def findScopedPar(theDict, scope, name): """ Find the given par. Return tuple: (its own (sub-)dict, its value). """ # Do not search (like findFirstPar), but go right to the correct # sub-section, and pick it up. Assume it is there as stated. if len(scope): theDict = theDict[scope] # ! only goes one level deep - enhance ! return theDict, theDict[name] # KeyError if unfound def setPar(theDict, name, value): """ Sets a par's value without having to give its scope/section. """ section, previousVal = findFirstPar(theDict, name) # "section" is the actual object, not a copy section[name] = value def mergeConfigObj(configObj, inputDict): """ Merge the inputDict values into an existing given configObj instance. The inputDict is a "flat" dict - it has no sections/sub-sections. The configObj may have sub-sections nested to any depth. This will raise a DuplicateKeyError if one of the inputDict keys is used more than once in configObj (e.g. within two different sub-sections). """ # Expanded upon Warren's version in astrodrizzle # Verify that all inputDict keys in configObj are unique within configObj for key in inputDict: if countKey(configObj, key) > 1: raise DuplicateKeyError(key) # Now update configObj with each inputDict item for key in inputDict: setPar(configObj, key, inputDict[key]) def integrityTestAllPkgCfgFiles(pkgObj, output=True): """ Given a package OBJECT, inspect it and find all installed .cfg file- using tasks under it. Then them one at a time via integrityTestTaskCfgFile, and report any/all errors. """ if type(pkgObj) != type(os): raise ValueError("Expected module arg, got: " + str(type(pkgObj))) taskDict = findAllCfgTasksUnderDir(os.path.dirname(pkgObj.__file__)) # taskDict is { cfgFileName : taskName } errors = [] for fname in taskDict: taskName = taskDict[fname] try: if taskName: if output: print('In '+pkgObj.__name__+', checking task: '+ taskName+', file: '+fname) integrityTestTaskCfgFile(taskName, fname) except Exception as e: errors.append(str(e)) if len(errors) != 0: raise ValueError('Errors found while integrity testing .cfg ' + 'file(s) found under "' + pkgObj.__name__ + '":\n' + ('\n'.join(errors))) def integrityTestTaskCfgFile(taskName, cfgFileName=None): """ For a given task, inspect the given .cfg file (or simply find/use its installed .cfg file), and check those values against the defaults found in the installed .cfgspc file. They should be the same. If the file name is not given, the installed one is found and used. """ from . import teal # don't import above, to avoid circular import (may need to mv) if not cfgFileName: ignored, cfgFileName = findCfgFileForPkg(taskName, '.cfg') diffDict = teal.diffFromDefaults(cfgFileName, report=False) if len(diffDict) < 1: return # no error msg = 'The following par:value pairs from "'+cfgFileName+ \ '" are not the correct defaults: '+str(diffDict) raise RuntimeError(msg) class ConfigObjPars(taskpars.TaskPars, configobj.ConfigObj): """ This represents a task's dict of ConfigObj parameters. """ def __init__(self, cfgFileName, forUseWithEpar=True, setAllToDefaults=False, strict=True, associatedPkg=None, forceReadOnly=False): """ cfgFileName - string path/name of .cfg file forUseWithEpar - bool - will this be used in EPAR? setAllToDefaults - <True, False, or string> string is pkg name to import strict - bool - level of error/warning severity associatedPkg - loaded package object forceReadOnly - bool - make the .cfg file read-only """ self._forUseWithEpar = forUseWithEpar self._rcDir = getAppDir() self._allTriggers = None # all known triggers in this object self._allDepdcs = None # all known dependencies in this object self._allExecutes = None # all known codes-to-execute in this object self._neverWrite = [] # all keys which are NOT written out to .cfg self._debugLogger = None self._debugYetToPost = [] self.__assocPkg = associatedPkg # The __paramList pointer remains the same for the life of this object self.__paramList = [] # Set up ConfigObj stuff if not setAllToDefaults and not os.path.isfile(cfgFileName): raise ValueError("Config file not found: " + cfgFileName) self.__taskName = '' if setAllToDefaults: # they may not have given us a real file name here since they # just want defaults (in .cfgspc) so don't be too picky about # finding and reading the file. if isinstance(setAllToDefaults, str): # here they have very clearly said to load only the defaults # using the given name as the package name - below we will # have it imported in _findAssociatedConfigSpecFile() self.__taskName = setAllToDefaults setAllToDefaults = True cfgFileName = '' # ignore any given .cfg file, don't need one else: possible = os.path.splitext(os.path.basename(cfgFileName))[0] if os.path.isfile(cfgFileName): self.__taskName = getEmbeddedKeyVal(cfgFileName, TASK_NAME_KEY, possible) else: self.__taskName = possible else: # this is the real deal, expect a real file name self.__taskName = getEmbeddedKeyVal(cfgFileName, TASK_NAME_KEY) if forceReadOnly: checkSetReadOnly(cfgFileName) # Find the associated .cfgspc file (first make sure we weren't # given one by mistake) if not cfgFileName.endswith('.cfg') and \ self.__taskName.find('(default=') >= 0: # Handle case where they gave us a .cfgspc by mistake (no .cfg) # (basically reset a few things) cfgSpecPath = os.path.realpath(cfgFileName) setAllToDefaults = True cfgFileName = '' sigStr = getEmbeddedKeyVal(cfgSpecPath, TASK_NAME_KEY, '') self.__taskName = vtor_checks.sigStrToKwArgsDict(sigStr)['default'] else: cfgSpecPath = self._findAssociatedConfigSpecFile(cfgFileName) if not os.path.exists(cfgSpecPath): raise ValueError("Matching configspec not found! Expected: " + cfgSpecPath) self.debug('ConfigObjPars: .cfg='+str(cfgFileName)+ \ ', .cfgspc='+str(cfgSpecPath)+ \ ', defaults='+str(setAllToDefaults)+', strict='+str(strict)) # Run the ConfigObj ctor. The result of this (if !setAllToDefaults) # is the exact copy of the input file as a dict (ConfigObj). If the # infile had extra pars or missing pars, they are still that way here. if setAllToDefaults: configobj.ConfigObj.__init__(self, configspec=cfgSpecPath) else: configobj.ConfigObj.__init__(self, os.path.abspath(cfgFileName), configspec=cfgSpecPath) # Before we validate (and fill in missing pars), find any lost pars # via this (somewhat kludgy) method suggested by ConfigObj folks. missing = '' # assume no .cfg file if strict and (not setAllToDefaults): # don't even populate this if not strict missing = findTheLost(os.path.abspath(cfgFileName), cfgSpecPath) # Validate it here. We can't skip this step even if we are just # setting all to defaults, since this sets the values. # NOTE - this fills in values for any missing pars ! AND, if our # .cfgspc sets defaults vals, then missing pars are not an error... self._vtor = validate.Validator(vtor_checks.FUNC_DICT) # 'ans' will be True, False, or a dict (anything but True is bad) ans = self.validate(self._vtor, preserve_errors=True, copy=setAllToDefaults) # Note: before the call to validate(), the list returned from # self.keys() is in the order found in self.filename. If that file # was missing items that are in the .cfgspc, they will now show up # in self.keys(), but not necessarily in the same order as the .cfgspc hasTypeErr = ans != True extra = self.listTheExtras(True) # DEAL WITH ERRORS (in this way) # # wrong par type: # strict -> severe error* # not -> severe error # extra par(s) found: # strict -> severe error # not -> warn* # missing par(s): # strict -> warn # not - be silent # # *severe - if in GUI, pop up error & stop (e.g. file load), else raise # *warn - if in GUI, pop up warning, else print it to screen if extra or missing or hasTypeErr: flatStr = '' if ans == False: flatStr = "All values are invalid!" if ans != True and ans != False: flatStr = flattened2str(configobj.flatten_errors(self, ans)) if missing: flatStr += "\n\n"+missing if extra: flatStr += "\n\n"+extra msg = "Validation warnings for: " if hasTypeErr or (strict and extra): msg = "Validation errors for: " msg = msg+os.path.realpath(cfgFileName)+\ "\n\n"+flatStr.strip('\n') if hasTypeErr or (strict and extra): raise RuntimeError(msg) else: # just inform them, but don't throw anything print(msg.replace('\n\n','\n')) # get the initial param list out of the ConfigObj dict self.syncParamList(True) # take note of all trigger logic self.debug(self.triggerLogicToStr()) # see if we are using a package with it's own run() function self._runFunc = None self._helpFunc = None if self.__assocPkg is not None: if hasattr(self.__assocPkg, 'run'): self._runFunc = self.__assocPkg.run if hasattr(self.__assocPkg, 'getHelpAsString'): self._helpFunc = self.__assocPkg.getHelpAsString def setDebugLogger(self, obj): # set the object we can use to post debugging info self._debugLogger = obj # now that we have one, post anything we have saved up (and clear list) if obj and len(self._debugYetToPost) > 0: for msg in self._debugYetToPost: self._debugLogger.debug(msg) self._debugYetToPost = [] def debug(self, msg): if self._debugLogger: self._debugLogger.debug(msg) else: # else just hold onto it until we do have a logger -during the # init phase we may not yet have a logger, yet have stuff to log self._debugYetToPost.append(msg) # add to our little cache def getDefaultSaveFilename(self, stub=False): """ Return name of file where we are expected to be saved if no files for this task have ever been saved, and the user wishes to save. If stub is True, the result will be <dir>/<taskname>_stub.cfg instead of <dir>/<taskname>.cfg. """ if stub: return self._rcDir+os.sep+self.__taskName+'_stub.cfg' else: return self._rcDir+os.sep+self.__taskName+'.cfg' def syncParamList(self, firstTime, preserve_order=True): """ Set or reset the internal param list from the dict's contents. """ # See the note in setParam about this design. # Get latest par values from dict. Make sure we do not # change the id of the __paramList pointer here. new_list = self._getParamsFromConfigDict(self, initialPass=firstTime) # dumpCfgspcTo=sys.stdout) # Have to add this odd last one for the sake of the GUI (still?) if self._forUseWithEpar: new_list.append(basicpar.IrafParS(['$nargs','s','h','N'])) if len(self.__paramList) > 0 and preserve_order: # Here we have the most up-to-date data from the actual data # model, the ConfigObj dict, and we need to use it to fill in # our param list. BUT, we need to preserve the order our list # has had up until now (by unique parameter name). namesInOrder = [p.fullName() for p in self.__paramList] if len(namesInOrder) != len(new_list): raise ValueError( 'Mismatch in num pars, had: ' + str(len(namesInOrder)) + ', now we have: ' + str(len(new_list)) + ', ' + str([p.fullName() for p in new_list])) self.__paramList[:] = [] # clear list, keep same pointer # create a flat dict view of new_list, for ease of use in next step new_list_dict = {} # can do in one step in v2.7 for par in new_list: new_list_dict[par.fullName()] = par # populate for fn in namesInOrder: self.__paramList.append(new_list_dict[fn]) else: # Here we just take the data in whatever order it came. self.__paramList[:] = new_list # keep same list pointer def getName(self): return self.__taskName def getPkgname(self): return '' # subclasses override w/ a sensible value def getParList(self, docopy=False): """ Return a list of parameter objects. docopy is ignored as the returned value is not a copy. """ return self.__paramList def getDefaultParList(self): """ Return a par list just like ours, but with all default values. """ # The code below (create a new set-to-dflts obj) is correct, but it # adds a tenth of a second to startup. Clicking "Defaults" in the # GUI does not call this. But this can be used to set the order seen. # But first check for rare case of no cfg file name if self.filename is None: # this is a .cfgspc-only kind of object so far self.filename = self.getDefaultSaveFilename(stub=True) return copy.deepcopy(self.__paramList) tmpObj = ConfigObjPars(self.filename, associatedPkg=self.__assocPkg, setAllToDefaults=True, strict=False) return tmpObj.getParList() def getFilename(self): if self.filename in (None, ''): return self.getDefaultSaveFilename() else: return self.filename def getAssocPkg(self): return self.__assocPkg def canExecute(self): return self._runFunc is not None def isSameTaskAs(self, aCfgObjPrs): """ Return True if the passed in object is for the same task as we are. """ return aCfgObjPrs.getName() == self.getName() # def strictUpdate(self, aDict): # """ Override the current values with those in the given dict. This # is like dict's update, except it doesn't allow new keys and it # verifies the values (it does?!) """ # if aDict is None: # return # for k in aDict: # v = aDict[k] # print("Skipping ovverride key = "+k+", val = "+str(v)) def setParam(self, name, val, scope='', check=1, idxHint=None): """ Find the ConfigObj entry. Update the __paramList. """ theDict, oldVal = findScopedPar(self, scope, name) # Set the value, even if invalid. It needs to be set before # the validation step (next). theDict[name] = val # If need be, check the proposed value. Ideally, we'd like to # (somehow elegantly) only check this one item. For now, the best # shortcut is to only validate this section. if check: ans=self.validate(self._vtor, preserve_errors=True, section=theDict) if ans != True: flatStr = "All values are invalid!" if ans != False: flatStr = flattened2str(configobj.flatten_errors(self, ans)) raise RuntimeError("Validation error: "+flatStr) # Note - this design needs work. Right now there are two copies # of the data: the ConfigObj dict, and the __paramList ... # We rely on the idxHint arg so we don't have to search the __paramList # every time this is called, which could really slows things down. if idxHint is None: raise ValueError("ConfigObjPars relies on a valid idxHint") if name != self.__paramList[idxHint].name: raise ValueError( 'Error in setParam, name: "' + name + '" != name at idxHint: "' + self.__paramList[idxHint].name + '", idxHint: ' + str(idxHint)) self.__paramList[idxHint].set(val) def saveParList(self, *args, **kw): """Write parameter data to filename (string or filehandle)""" if 'filename' in kw: filename = kw['filename'] if not filename: filename = self.getFilename() if not filename: raise ValueError("No filename specified to save parameters") if hasattr(filename,'write'): fh = filename absFileName = os.path.abspath(fh.name) else: absFileName = os.path.expanduser(filename) absDir = os.path.dirname(absFileName) if len(absDir) and not os.path.isdir(absDir): os.makedirs(absDir) fh = open(absFileName,'w') numpars = len(self.__paramList) if self._forUseWithEpar: numpars -= 1 if not self.final_comment: self.final_comment = [''] # force \n at EOF # Empty the ConfigObj version of section.defaults since that is based # on an assumption incorrect for us, and override with our own list. # THIS IS A BIT OF MONKEY-PATCHING! WATCH FUTURE VERSION CHANGES! # See Trac ticket #762. while len(self.defaults): self.defaults.pop(-1) # empty it, keeping ref for key in self._neverWrite: self.defaults.append(key) # Note also that we are only overwriting the top/main section's # "defaults" list, but EVERY [sub-]section has such an attribute... # Now write to file, delegating work to ConfigObj (note that ConfigObj # write() skips any items listed by name in the self.defaults list) self.write(fh) fh.close() retval = str(numpars) + " parameters written to " + absFileName self.filename = absFileName # reset our own ConfigObj filename attr self.debug('Keys not written: '+str(self.defaults)) return retval def run(self, *args, **kw): """ This may be overridden by a subclass. """ if self._runFunc is not None: # remove the two args sent by EditParDialog which we do not use if 'mode' in kw: kw.pop('mode') if '_save' in kw: kw.pop('_save') return self._runFunc(self, *args, **kw) else: raise taskpars.NoExecError('No way to run task "'+self.__taskName+\ '". You must either override the "run" method in your '+ \ 'ConfigObjPars subclass, or you must supply a "run" '+ \ 'function in your package.') def triggerLogicToStr(self): """ Print all the trigger logic to a string and return it. """ try: import json except ImportError: return "Cannot dump triggers/dependencies/executes (need json)" retval = "TRIGGERS:\n"+json.dumps(self._allTriggers, indent=3) retval += "\nDEPENDENCIES:\n"+json.dumps(self._allDepdcs, indent=3) retval += "\nTO EXECUTE:\n"+json.dumps(self._allExecutes, indent=3) retval += "\n" return retval def getHelpAsString(self): """ This may be overridden by a subclass. """ if self._helpFunc is not None: return self._helpFunc() else: return 'No help string found for task "'+self.__taskName+ \ '". \n\nThe developer must either override the '+\ 'getHelpAsString() method in their ConfigObjPars \n'+ \ 'subclass, or they must supply such a function in their package.' def _findAssociatedConfigSpecFile(self, cfgFileName): """ Given a config file, find its associated config-spec file, and return the full pathname of the file. """ # Handle simplest 2 cases first: co-located or local .cfgspc file retval = "."+os.sep+self.__taskName+".cfgspc" if os.path.isfile(retval): return retval retval = os.path.dirname(cfgFileName)+os.sep+self.__taskName+".cfgspc" if os.path.isfile(retval): return retval # Also try the resource dir retval = self.getDefaultSaveFilename()+'spc' # .cfgspc if os.path.isfile(retval): return retval # Now try and see if there is a matching .cfgspc file in/under an # associated package, if one is defined. if self.__assocPkg is not None: x, theFile = findCfgFileForPkg(None, '.cfgspc', pkgObj = self.__assocPkg, taskName = self.__taskName) return theFile # Finally try to import the task name and see if there is a .cfgspc # file in that directory x, theFile = findCfgFileForPkg(self.__taskName, '.cfgspc', taskName = self.__taskName) if os.path.exists(theFile): return theFile # unfound raise NoCfgFileError('Unfound config-spec file for task: "'+ \ self.__taskName+'"') def _getParamsFromConfigDict(self, cfgObj, scopePrefix='', initialPass=False, dumpCfgspcTo=None): """ Walk the given ConfigObj dict pulling out IRAF-like parameters into a list. Since this operates on a dict this can be called recursively. This is also our chance to find and pull out triggers and such dependencies. """ # init retval = [] if initialPass and len(scopePrefix) < 1: self._posArgs = [] # positional args [2-tuples]: (index,scopedName) # FOR SECURITY: the following 3 chunks of data, # _allTriggers, _allDepdcs, _allExecutes, # are collected ONLY from the .cfgspc file self._allTriggers = {} self._allDepdcs = {} self._allExecutes = {} # start walking ("tell yer story walkin, buddy") # NOTE: this relies on the "in" operator returning keys in the # order that they exist in the dict (which depends on ConfigObj keeping # the order they were found in the original file) for key in cfgObj: val = cfgObj[key] # Do we need to skip this - if not a par, like a rule or something toBeHidden = isHiddenName(key) if toBeHidden: if key not in self._neverWrite and key != TASK_NAME_KEY: self._neverWrite.append(key) # yes TASK_NAME_KEY is hidden, but it IS output to the .cfg # a section if isinstance(val, dict): if not toBeHidden: if len(list(val.keys()))>0 and len(retval)>0: # Here is where we sneak in the section comment # This is so incredibly kludgy (as the code was), it # MUST be revamped eventually! This is for the epar GUI. prevPar = retval[-1] # Use the key (or its comment?) as the section header prevPar.set(prevPar.get('p_prompt')+'\n\n'+key, field='p_prompt', check=0) if dumpCfgspcTo: dumpCfgspcTo.write('\n['+key+']\n') # a logical grouping (append its params) pfx = scopePrefix+'.'+key pfx = pfx.strip('.') retval = retval + self._getParamsFromConfigDict(val, pfx, initialPass, dumpCfgspcTo) # recurse else: # a param fields = [] choicesOrMin = None fields.append(key) # name dtype = 's' cspc = None if cfgObj.configspec: cspc = cfgObj.configspec.get(key) # None if not found chk_func_name = '' chk_args_dict = {} if cspc: chk_func_name = cspc[:cspc.find('(')] chk_args_dict = vtor_checks.sigStrToKwArgsDict(cspc) if chk_func_name.find('option') >= 0: dtype = 's' # convert the choices string to a list (to weed out kwds) x = cspc[cspc.find('(')+1:-1] # just the options() args # cspc e.g.: option_kw("poly5","nearest","linear", default="poly5", comment="Interpolant (poly5,nearest,linear)") x = x.split(',') # tokenize # but! comment value may have commas in it, find it # using it's equal sign, rm all after it has_eq = [i for i in x if i.find('=')>=0] if len(has_eq) > 0: x = x[: x.index(has_eq[0]) ] # rm spaces, extra quotes; rm kywd arg pairs x = [i.strip("' ") for i in x if i.find('=')<0] choicesOrMin = '|'+'|'.join(x)+'|' # IRAF format for enums elif chk_func_name.find('boolean') >= 0: dtype = 'b' elif chk_func_name.find('float_or_') >= 0: dtype = 'r' elif chk_func_name.find('float') >= 0: dtype = 'R' elif chk_func_name.find('integer_or_') >= 0: dtype = 'i' elif chk_func_name.find('integer') >= 0: dtype = 'I' elif chk_func_name.find('action') >= 0: dtype = 'z' fields.append(dtype) fields.append('a') if type(val)==bool: if val: fields.append('yes') else: fields.append('no') else: fields.append(val) fields.append(choicesOrMin) fields.append(None) # Primarily use description from .cfgspc file (0). But, allow # overrides from .cfg file (1) if different. dscrp0 = chk_args_dict.get('comment','').strip() # ok if missing dscrp1 = cfgObj.inline_comments[key] if dscrp1 is None: dscrp1 = '' while len(dscrp1) > 0 and dscrp1[0] in (' ','#'): dscrp1 = dscrp1[1:] # .cfg file comments start with '#' dscrp1 = dscrp1.strip() # Now, decide what to do/say about the descriptions if len(dscrp1) > 0: dscrp = dscrp0 if dscrp0 != dscrp1: # allow override if different dscrp = dscrp1+eparoption.DSCRPTN_FLAG # flag it if initialPass: if dscrp0 == '' and cspc is None: # this is a case where this par isn't in the # .cfgspc; ignore, it is caught/error later pass else: self.debug('Description of "'+key+ \ '" overridden, from: '+repr(dscrp0)+\ ' to: '+repr(dscrp1)) fields.append(dscrp) else: # set the field for the GUI fields.append(dscrp0) # ALSO set it in the dict so it is written to file later cfgObj.inline_comments[key] = '# '+dscrp0 # This little section, while never intended to be used during # normal operation, could save a lot of manual work. if dumpCfgspcTo: junk = cspc junk = key+' = '+junk.strip() if junk.find(' comment=')<0: junk = junk[:-1]+", comment="+ \ repr(irafutils.stripQuotes(dscrp1.strip()))+")" dumpCfgspcTo.write(junk+'\n') # Create the par if not toBeHidden or chk_func_name.find('action')==0: par = basicpar.parFactory(fields, True) par.setScope(scopePrefix) retval.append(par) # else this is a hidden key # The next few items require a fully scoped name absKeyName = scopePrefix+'.'+key # assumed to be unique # Check for pars marked to be positional args if initialPass: pos = chk_args_dict.get('pos') if pos: # we'll sort them later, on demand self._posArgs.append( (int(pos), scopePrefix, key) ) # Check for triggers and/or dependencies if initialPass: # What triggers what? (thats why theres an 's' in the kwd) # try "trigger" (old) if chk_args_dict.get('trigger'): print("WARNING: outdated version of .cfgspc!! for "+ self.__taskName+", 'trigger' unused for "+ absKeyName) # try "triggers" trgs = chk_args_dict.get('triggers') if trgs and len(trgs)>0: # eg. _allTriggers['STEP2.xy'] == ('_rule1_','_rule3_') if absKeyName in self._allTriggers: raise ValueError('More than 1 of these in .cfgspc?: ' + absKeyName) # we force this to always be a sequence if isinstance(trgs, (list,tuple)): self._allTriggers[absKeyName] = trgs else: self._allTriggers[absKeyName] = (trgs,) # try "executes" excs = chk_args_dict.get('executes') if excs and len(excs)>0: # eg. _allExecutes['STEP2.xy'] == ('_rule1_','_rule3_') if absKeyName in self._allExecutes: raise ValueError('More than 1 of these in .cfgspc?: ' + absKeyName) # we force this to always be a sequence if isinstance(excs, (list,tuple)): self._allExecutes[absKeyName] = excs else: self._allExecutes[absKeyName] = (excs,) # Dependencies? (besides these used here, may someday # add: 'range_from', 'warn_if', etc.) depName = None if not depName: depType = 'active_if' depName = chk_args_dict.get(depType) # e.g. =='_rule1_' if not depName: depType = 'inactive_if' depName = chk_args_dict.get(depType) if not depName: depType = 'is_set_by' depName = chk_args_dict.get(depType) if not depName: depType = 'set_yes_if' depName = chk_args_dict.get(depType) if not depName: depType = 'set_no_if' depName = chk_args_dict.get(depType) if not depName: depType = 'is_disabled_by' depName = chk_args_dict.get(depType) # NOTE - the above few lines stops at the first dependency # found (depName) for a given par. If, in the future a # given par can have >1 dependency than we need to revamp!! if depName: # Add to _allDepdcs dict: (val is dict of pars:types) # # e.g. _allDepdcs['_rule1_'] == \ # {'STEP3.ra': 'active_if', # 'STEP3.dec': 'active_if', # 'STEP3.azimuth': 'inactive_if'} if depName in self._allDepdcs: thisRulesDict = self._allDepdcs[depName] if absKeyName in thisRulesDict: raise ValueError( 'Cant yet handle multiple actions for the ' + 'same par and the same rule. For "' + depName + '" dict was: ' + str(thisRulesDict) + ' while trying to add to it: ' + str({absKeyName: depType})) thisRulesDict[absKeyName] = depType else: self._allDepdcs[depName] = {absKeyName:depType} # else no dependencies found for this chk_args_dict return retval def getTriggerStrings(self, parScope, parName): """ For a given item (scope + name), return all strings (in a tuple) that it is meant to trigger, if any exist. Returns None is none. """ # The data structure of _allTriggers was chosen for how easily/quickly # this particular access can be made here. fullName = parScope+'.'+parName return self._allTriggers.get(fullName) # returns None if unfound def getParsWhoDependOn(self, ruleName): """ Find any parameters which depend on the given trigger name. Returns None or a dict of {scopedName: dependencyName} from _allDepdcs. """ # The data structure of _allDepdcs was chosen for how easily/quickly # this particular access can be made here. return self._allDepdcs.get(ruleName) def getExecuteStrings(self, parScope, parName): """ For a given item (scope + name), return all strings (in a tuple) that it is meant to execute, if any exist. Returns None is none. """ # The data structure of _allExecutes was chosen for how easily/quickly # this particular access can be made here. fullName = parScope+'.'+parName return self._allExecutes.get(fullName) # returns None if unfound def getPosArgs(self): """ Return a list, in order, of any parameters marked with "pos=N" in the .cfgspc file. """ if len(self._posArgs) < 1: return [] # The first item in the tuple is the index, so we now sort by it self._posArgs.sort() # Build a return list retval = [] for idx, scope, name in self._posArgs: theDict, val = findScopedPar(self, scope, name) retval.append(val) return retval def getKwdArgs(self, flatten = False): """ Return a dict of all normal dict parameters - that is, all parameters NOT marked with "pos=N" in the .cfgspc file. This will also exclude all hidden parameters (metadata, rules, etc). """ # Start with a full deep-copy. What complicates this method is the # idea of sub-sections. This dict can have dicts as values, and so on. dcopy = self.dict() # ConfigObj docs say this is a deep-copy # First go through the dict removing all positional args for idx,scope,name in self._posArgs: theDict, val = findScopedPar(dcopy, scope, name) # 'theDict' may be dcopy, or it may be a dict under it theDict.pop(name) # Then go through the dict removing all hidden items ('_item_name_') for k in list(dcopy.keys()): if isHiddenName(k): dcopy.pop(k) # Done with the nominal operation if not flatten: return dcopy # They have asked us to flatten the structure - to bring all parameters # up to the top level, even if they are in sub-sections. So we look # for values that are dicts. We will throw something if we end up # with name collisions at the top level as a result of this. return flattenDictTree(dcopy) def canPerformValidation(self): """ Override this so we can do our own validation. tryValue() will be called as a result. """ return True def knowAsNative(self): """ Override so we can keep native types in the internal dict. """ return True def tryValue(self, name, val, scope=''): """ For the given item name (and scope), we are being asked to try the given value to see if it would pass validation. We are not to set it, but just try it. We return a tuple: If it fails, we return: (False, the last known valid value). On success, we return: (True, None). """ # SIMILARITY BETWEEN THIS AND setParam() SHOULD BE CONSOLIDATED! # Set the value, even if invalid. It needs to be set before # the validation step (next). theDict, oldVal = findScopedPar(self, scope, name) if oldVal == val: return (True, None) # assume oldVal is valid theDict[name] = val # Check the proposed value. Ideally, we'd like to # (somehow elegantly) only check this one item. For now, the best # shortcut is to only validate this section. ans=self.validate(self._vtor, preserve_errors=True, section=theDict) # No matter what ans is, immediately return the item to its original # value since we are only checking the value here - not setting. theDict[name] = oldVal # Now see what the validation check said errStr = '' if ans != True: flatStr = "All values are invalid!" if ans != False: flatStr = flattened2str(configobj.flatten_errors(self, ans)) errStr = "Validation error: "+flatStr # for now this info is unused # Done if len(errStr): return (False, oldVal) # was an error else: return (True, None) # val is OK def listTheExtras(self, deleteAlso): """ Use ConfigObj's get_extra_values() call to find any extra/unknown parameters we may have loaded. Return a string similar to findTheLost. If deleteAlso is True, this will also delete any extra/unknown items. """ # get list of extras extras = configobj.get_extra_values(self) # extras is in format: [(sections, key), (sections, key), ] # but we need: [(sections, key, result), ...] - set all results to # a bool just to make it the right shape. BUT, since we are in # here anyway, make that bool mean something - hide info in it about # whether that extra item is a section (1) or just a single par (0) # # simplified, this is: expanded = [ (x+(abool,)) for x in extras] expanded = [ (x+ \ ( bool(len(x[0])<1 and hasattr(self[x[1]], 'keys')), ) \ ) for x in extras] retval = '' if expanded: retval = flattened2str(expanded, extra=1) # but before we return, delete them (from ourself!) if requested to if deleteAlso: for tup_to_del in extras: target = self # descend the tree to the dict where this items is located. # (this works because target is not a copy (because the dict # type is mutable)) location = tup_to_del[0] for subdict in location: target = target[subdict] # delete it target.pop(tup_to_del[1]) return retval # ---------------------------- helper functions -------------------------------- def findTheLost(config_file, configspec_file, skipHidden=True): """ Find any lost/missing parameters in this cfg file, compared to what the .cfgspc says should be there. This method is recommended by the ConfigObj docs. Return a stringified list of item errors. """ # do some sanity checking, but don't (yet) make this a serious error if not os.path.exists(config_file): print("ERROR: Config file not found: "+config_file) return [] if not os.path.exists(configspec_file): print("ERROR: Configspec file not found: "+configspec_file) return [] tmpObj = configobj.ConfigObj(config_file, configspec=configspec_file) simval = configobj.SimpleVal() test = tmpObj.validate(simval) if test == True: return [] # If we get here, there is a dict returned of {key1: bool, key2: bool} # which matches the shape of the config obj. We need to walk it to # find the Falses, since they are the missing pars. missing = [] flattened = configobj.flatten_errors(tmpObj, test) # But, before we move on, skip/eliminate any 'hidden' items from our list, # since hidden items are really supposed to be missing from the .cfg file. if len(flattened) > 0 and skipHidden: keepers = [] for tup in flattened: keep = True # hidden section if len(tup[0])>0 and isHiddenName(tup[0][-1]): keep = False # hidden par (in a section, or at the top level) elif tup[1] is not None and isHiddenName(tup[1]): keep = False if keep: keepers.append(tup) flattened = keepers flatStr = flattened2str(flattened, missing=True) return flatStr def isHiddenName(astr): """ Return True if this string name denotes a hidden par or section """ if astr is not None and len(astr) > 2 and astr.startswith('_') and \ astr.endswith('_'): return True else: return False def flattened2str(flattened, missing=False, extra=False): """ Return a pretty-printed multi-line string version of the output of flatten_errors. Know that flattened comes in the form of a list of keys that failed. Each member of the list is a tuple:: ([list of sections...], key, result) so we turn that into a string. Set missing to True if all the input problems are from missing items. Set extra to True if all the input problems are from extra items. """ if flattened is None or len(flattened) < 1: return '' retval = '' for sections, key, result in flattened: # Name the section and item, to start the message line if sections is None or len(sections) == 0: retval += '\t"'+key+'"' elif len(sections) == 1: if key is None: # a whole section is missing at the top-level; see if hidden junk = sections[0] if isHiddenName(junk): continue # this missing or extra section is not an error else: retval += '\tSection "'+sections[0]+'"' else: retval += '\t"'+sections[0]+'.'+key+'"' else: # len > 1 joined = '.'.join(sections) joined = '"'+joined+'"' if key is None: retval += '\tSection '+joined else: retval += '\t"'+key+'" from '+joined # End the msg line with "what seems to be the trouble" with this one if missing and result==False: retval += ' is missing.' elif extra: if result: retval += ' is an unexpected section. Is your file out of date?' else: retval += ' is an unexpected parameter. Is your file out of date?' elif isinstance(result, bool): retval += ' has an invalid value' else: retval += ' is invalid, '+result.message retval += '\n\n' return retval.rstrip() �����������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/compmixin.py����������������������������������������������������������������0000644�0001750�0001750�00000004451�14214070451�016223� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # """ This module is from Lennart Regebro's ComparableMixin class, available at: http://regebro.wordpress.com/2010/12/13/ python-implementing-rich-comparison-the-correct-way/ The idea is to prevent you from having to define lt,le,eq,ne,etc... This may no longer be necessary after the functools total_ordering decorator (Python v2.7) is available on all Python versions supported by our software. For simple comparisons, all that is necessary is to derive your class from ComparableMixin and override the _cmpkey() method. For more complex comparisons (where type-checking needs to occur and comparisons to other types are allowed), simply override _compare() instead of _cmpkey(). """ class ComparableMixin: def _compare(self, other, method): try: return method(self._cmpkey(), other._cmpkey()) except (AttributeError, TypeError): # _cmpkey not implemented, or return different type, # so I can't compare with "other". return NotImplemented def __lt__(self, other): return self._compare(other, lambda s,o: s < o) def __le__(self, other): return self._compare(other, lambda s,o: s <= o) def __eq__(self, other): return self._compare(other, lambda s,o: s == o) def __ge__(self, other): return self._compare(other, lambda s,o: s >= o) def __gt__(self, other): return self._compare(other, lambda s,o: s > o) def __ne__(self, other): return self._compare(other, lambda s,o: s != o) class ComparableIntBaseMixin(ComparableMixin): """ For those classes which, at heart, are comparable to integers. """ def _compare(self, other, method): if isinstance(other, self.__class__): # two objects of same class return method(self._cmpkey(), other._cmpkey()) else: return method(int(self._cmpkey()), int(other)) class ComparableFloatBaseMixin(ComparableMixin): """ For those classes which, at heart, are comparable to floats. """ def _compare(self, other, method): if isinstance(other, self.__class__): # two objects of same class return method(self._cmpkey(), other._cmpkey()) else: return method(float(self._cmpkey()), float(other)) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/dialog.py�������������������������������������������������������������������0000644�0001750�0001750�00000003562�14214070451�015461� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#### # Class Dialog # # Purpose # Base class for many dialog box classes. #### """ $Id$ """ import sys from tkinter import * # noqa class Dialog: def __init__(self, master): self.master = master self.top = Toplevel(self.master) self.top.title(self.__class__.__name__) self.top.minsize(1, 1) self.myWaitVar = str(self.top) + 'EndDialogVar' def Show(self): self.SetupDialog() self.CenterDialog() self.top.deiconify() self.top.focus() def TerminateDialog(self, withValue): self.top.setvar(self.myWaitVar, withValue) self.top.withdraw() def DialogCleanup(self): self.top.destroy() self.master.focus() def SetupDialog(self): pass def CenterDialog(self): self.top.withdraw() self.top.update_idletasks() w = self.top.winfo_screenwidth() h = self.top.winfo_screenheight() reqw = self.top.winfo_reqwidth() reqh = self.top.winfo_reqheight() centerx = str((w-reqw)//2) centery = str((h-reqh)//2 - 100) geomStr = "+" + centerx + "+" + centery self.top.geometry(geomStr) #### # Class ModalDialog # # Purpose # Base class for many modal dialog box classes. #### class ModalDialog(Dialog): def __init__(self, master): Dialog__init__(self, master) def Show(self): self.SetupDialog() self.CenterDialog() try: self.top.grab_set() # make it modal except TclError: # This fails on Linux, but does it really HAVE to be modal if sys.platform.lower().find('linux') >= 0: pass else: raise self.top.focus() self.top.deiconify() self.top.waitvar(self.myWaitVar) return int(self.top.getvar(self.myWaitVar)) ����������������������������������������������������������������������������������������������������������������������������������������������././@PaxHeader��������������������������������������������������������������������������������������0000000�0000000�0000000�00000000026�00000000000�010213� x����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������22 mtime=1647341865.0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyraf-2.2.1/pyraf/tools/editpar.py������������������������������������������������������������������0000644�0001750�0001750�00000211745�14214070451�015656� 0����������������������������������������������������������������������������������������������������ustar�00oles����������������������������oles�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""module 'editpar.py' -- main module for generating the EPAR task editor $Id$ Taken from pyraf/lib/epar.py, originally signed "M.D. De La Pena, 2000 Feb. 4" """ import os import sys import tempfile import time from . import capable if capable.OF_GRAPHICS: from tkinter import _default_root from tkinter import * from tkinter.filedialog import asksaveasfilename from tkinter.messagebox import askokcancel, askyesno, showwarning # stsci.tools modules from .irafglobals import userWorkingHome from . import basicpar, eparoption, irafutils, taskpars # Constants MINVIEW = 500 MINPARAMS = 25 INPUTWIDTH = 10 VALUEWIDTH = 21 PROMPTWIDTH = 55 DFT_OPT_FILE = "epar.optionDB" TIP = "rollover" DBG = "debug" # The following action types are used within the GUI code. They define what # kind of GUI action actually caused a parameter's value to be adjusted. # This is meant to be like an enum. These values may appear in a task's # task.cfgspc file in a rule. In that file, the value 'always' may be used, in # addition to these values, to indicate a match to all possible action types. GROUP_ACTIONS = ('defaults','init','fopen','entry') # init -> startup of the GUI # defaults -> the user clicked the Defaults or Reset button # fopen -> the user loaded a config file # entry -> the user actually edited a parameter (via mouse or keyboard) # Use these values for startup geometry ***for now*** # PARENT is the main editor window PARENTX = 50 PARENTY = 50 # DCHILD[XY] are amounts each successive child shifts DCHILDX = 50 DCHILDY = 50 # CHILD[XY] is a PSET window CHILDX = PARENTX CHILDY = PARENTY # HELP[XY] is for the help as displayed in a window HELPX = 300 HELPY = 25 class UnfoundParamError(Exception): pass class EditParDialog: def __init__(self, theTask, parent=None, isChild=0, title="Parameter Editor", childList=None, resourceDir='.'): # Initialize status message stuff first thing self._leaveStatusMsgUntil = 0 self._msgHistory = [] # all msgs, of all kinds, since we opened self._statusMsgsToShow = [] # keep a *small* number of late msgs self.debug('Starting up the GUI!') # Call our (or a subclass's) _setTaskParsObj() method self._setTaskParsObj(theTask) # Now go back and ensure we have the full taskname; set up other items self._canceled = False self._executed = False self._guiName = title self.taskName = self._taskParsObj.getName() self.pkgName = self._taskParsObj.getPkgname() theParamList = self._taskParsObj.getParList(docopy=1) self._rcDir = resourceDir self.debug('TASK: '+self.taskName+', PKG: '+self.pkgName+ \ ', RC: '+self._rcDir) # setting _tmwm=1 is the slowest motion, 7 seems OK, 10 maybe too fast self._tmwm = int(os.getenv('TEAL_MOUSE_WHEEL_MULTIPLIER', 7)) # Get default parameter values for unlearn - watch return value # NOTE - this may edit/reorder the working paramList if not self._setupDefaultParamList(): return # Ignore the last parameter which is $nargs self.numParams = len(theParamList) - 1 # Set all default master GUI settings, then # allow subclasses to override them self._appName = "Par Editor" self._appHelpString = "No help yet created for this GUI editor" self._useSimpleAutoClose = False # certain buttons close GUI also self._showExecuteButton = True self._showSaveCloseOnExec = True self._saveAndCloseOnExec = True self._showFlaggingChoice = True self._flagNonDefaultVals = None # default not yet set self._showExtraHelpButton = False self._showHelpInBrowser = False self._knowTaskHelpIsHtml = False self._unpackagedTaskTitle = "Task" self._writeProtectOnSaveAs= True self._defaultsButtonTitle = "Defaults" self._optFile = DFT_OPT_FILE self._defSaveAsExt = '.cfg' # Colors self._frmeColor = None # frame of window self._taskColor = None # task label area self._bboxColor = None # button area self._entsColor = None # entries area self._flagColor = "red" # non-default values # give the subclass a chance to disagree self._overrideMasterSettings() # give the subclass a chance to disagree # any settings which depend on overrides if self._flagNonDefaultVals is None: self._flagNonDefaultVals = self._showFlaggingChoice # default # Create the root window as required, but hide it self.parent = parent if self.parent is None: global _default_root if _default_root is None: _default_root = irafutils.init_tk_default_root() # Track whether this is a parent or child window self.isChild = isChild # Set up a color for each of the backgrounds if self.isChild: # self._frmeColor = "LightSteelBlue" self.iconLabel = "EPAR Child" else: self.iconLabel = "EPAR Parent" # help windows do not exist yet self.eparHelpWin = None self.irafHelpWin = None self.logHistWin = None # no last focus widget self.lastFocusWidget = None # Generate the top epar window self.top = top = Toplevel(self.parent,bg=self._frmeColor,visual="best") self.top.withdraw() # hide it while we fill it up with stuff if len(self.pkgName): self.updateTitle(self.pkgName+"."+self.taskName) else: self.updateTitle(self.taskName) self.top.iconname(self.iconLabel) # Read in the tk options database file try: # User's current directory self.top.option_readfile(os.path.join(os.curdir, self._optFile)) except TclError: try: # User's startup directory self.top.option_readfile(os.path.join(userWorkingHome, self._optFile)) except TclError: try: # App default self.top.option_readfile(os.path.join(self._rcDir, self._optFile)) except TclError: if self._optFile != DFT_OPT_FILE: pass else: raise # Create an empty list to hold child dialogs # *** Not a good way, REDESIGN with Mediator! # Also, build the parent menu bar if self.parent is None: self.top.childList = [] elif childList is not None: # all children share a list self.top.childList = childList # Build the EPAR menu bar self.makeMenuBar(self.top) # Create a spacer Frame(self.top, bg=self._taskColor, height=10).pack(side=TOP, fill=X) # Print the package and task names self.printNames(self.top, self.taskName, self.pkgName) # Insert a spacer between the static text and the buttons Frame(self.top, bg=self._taskColor, height=15).pack(side=TOP, fill=X) # Set control buttons at the top of the frame self.buttonBox(self.top) # Insert a spacer between the static text and the buttons Frame(self.top, bg=self._entsColor, height=15).pack(side=TOP, fill=X) # Set up an information Frame at the bottom of the EPAR window # RESIZING is currently disabled. # Do this here so when resizing to a smaller sizes, the parameter # panel is reduced - not the information frame. self.top.status = Label(self.top, text="", relief=SUNKEN, borderwidth=1, anchor=W, bg=self._frmeColor) self.top.status.pack(side=BOTTOM, fill=X, padx=0, pady=3, ipady=3) # Set up a Frame to hold a scrollable Canvas self.top.f = frame = Frame(self.top, relief=RIDGE, borderwidth=1, bg=self._entsColor) # Overlay a Canvas which will hold a Frame self.top.f.canvas = canvas = Canvas(self.top.f, width=100, height=100, takefocus=FALSE, bg=self._entsColor, highlightbackground=self._entsColor) # highlightcolor="black" # black must be the default, since it is blk # Always build the scrollbar, even if number of parameters is small, # to allow window to be resized. # Attach a vertical Scrollbar to the Frame/Canvas self.top.f.vscroll = Scrollbar(self.top.f, orient=VERTICAL, width=11, relief=SUNKEN, activerelief=RAISED, takefocus=FALSE, bg=self._entsColor) canvas['yscrollcommand'] = self.top.f.vscroll.set self.top.f.vscroll['command'] = canvas.yview # Pack the Scrollbar self.top.f.vscroll.pack(side=RIGHT, fill=Y) # enable Page Up/Down keys scroll = canvas.yview_scroll top.bind('<Next>', lambda event, fs=scroll: fs(1, "pages")) top.bind('<Prior>', lambda event, fs=scroll: fs(-1, "pages")) # make up, down arrows and return/shift-return do same as Tab, Shift-Tab top.bind('<Up>', self.focusPrev) top.bind('<MouseWheel>', self.mwl) # on OSX, rolled up or down top.bind('<Button-4>', self.mwl) # on Linux, rolled up top.bind('<Button-5>', self.mwl) # on Linux, rolled down top.bind('<Down>', self.focusNext) top.bind('<Shift-Return>', self.focusPrev) top.bind('<Return>', self.focusNext) try: # special shift-tab binding needed for (some? all?) linux systems top.bind('<KeyPress-ISO_Left_Tab>', self.focusPrev) except TclError: # Ignore exception here, the binding can't be relevant # if ISO_Left_Tab is unknown. pass # Pack the Frame and Canvas canvas.pack(side=TOP, expand=TRUE, fill=BOTH) self.top.f.pack(side=TOP, expand=TRUE, fill=BOTH) # Define a Frame to contain the parameter information canvas.entries = Frame(canvas, bg=self._entsColor) # Generate the window to hold the Frame which sits on the Canvas cWindow = canvas.create_window(0, 0, anchor=NW, window=canvas.entries) # Insert a spacer between the Canvas and the information frame Frame(self.top, bg=self._entsColor, height=4).pack(side=TOP, fill=X) # The parent has the control, unless there are children # Fix the geometry of where the windows first appear on the screen if self.parent is None: #self.top.grab_set() # Position this dialog relative to the parent self.top.geometry("+%d+%d" % (PARENTX, PARENTY)) else: #self.parent.grab_release() #self.top.grab_set() # Declare the global variables so they can be updated global CHILDX global CHILDY # Position this dialog relative to the parent CHILDX = CHILDX + DCHILDX CHILDY = CHILDY + DCHILDY self.top.geometry("+%d+%d" % (CHILDX, CHILDY)) # # Now fill in the Canvas Window # # The makeEntries method creates the parameter entry Frame self.makeEntries(canvas.entries, self.top.status) # Force an update of the entry Frame canvas.entries.update() # Determine the size of the entry Frame width = canvas.entries.winfo_width() height = canvas.entries.winfo_height() # Reconfigure the Canvas size based on the Frame. if (self.numParams <= MINPARAMS): viewHeight = height else: # Set the minimum display viewHeight = MINVIEW # Scrollregion is based upon the full size of the entry Frame canvas.config(scrollregion=(0, 0, width, height)) # Smooth scroll self.yscrollincrement = 5 # changed Mar2010, had been 50 a long time canvas.config(yscrollincrement=self.yscrollincrement) # Set the actual viewable region for the Canvas canvas.config(width=width, height=viewHeight) # Force an update of the Canvas canvas.update() # Associate deletion of the main window to a Abort self.top.protocol("WM_DELETE_WINDOW", self.abort) # Trigger all widgets one time before starting in case they have # values which would run a trigger self.checkAllTriggers('init') # Set focus to first parameter self.setViewAtTop() # Finally show it self.top.update() self.top.deiconify() # Enable interactive resizing in height self.top.resizable(width=FALSE, height=TRUE) # Limit maximum window height width = self.top.winfo_width() height = self.top.winfo_height() + height - viewHeight self.top.maxsize(width=width, height=height) self.debug('showing '+self._appName+' main window') # run the mainloop if not self.isChild: self._preMainLoop() self.top.mainloop() self._postMainLoop() def _overrideMasterSettings(self): """ Hook for subclasses to override some attributes if wished. """ return def _preMainLoop(self): """ Hook for subclasses to override if wished. """ return def _postMainLoop(self): """ Hook for subclasses to override if wished. """ return def _showOpenButton(self): """ Should we show the "Open..." button? Subclasses override. """ return True def _setTaskParsObj(self, theTask): """ This method, meant to be overridden by subclasses, generates the _taskParsObj object. theTask can often be either a file name or a TaskPars subclass object. """ # Here we catch if this version is run by accident raise NotImplementedError("EditParDialog is not to be used directly") def _saveGuiSettings(self): """ Hook for subclasses to save off GUI settings somewhere. """ return # skip this by default def updateTitle(self, atitle): if atitle: self.top.title('%s: %s' % (self._guiName, atitle)) else: self.top.title('%s' % (self._guiName)) def checkAllTriggers(self, action): """ Go over all widgets and let them know they have been edited recently and they need to check for any trigger actions. This would be used right after all the widgets have their values set or forced (e.g. via setAllEntriesFromParList). """ for entry in self.entryNo: entry.widgetEdited(action=action, skipDups=False) def freshenFocus(self): """ Did something which requires a new look. Move scrollbar up. This often needs to be delayed a bit however, to let other events in the queue through first. """ self.top.update_idletasks() self.top.after(10, self.setViewAtTop) def setViewAtTop(self): self.entryNo[0].focus_set() self.top.f.canvas.xview_moveto(0.0) self.top.f.canvas.yview_moveto(0.0) def getTaskParsObj(self): """ Simple accessor. Return the _taskParsObj object. """ return self._taskParsObj def mwl(self, event): """Mouse Wheel - under tkinter we seem to need Tk v8.5+ for this """ if event.num == 4: # up on Linux self.top.f.canvas.yview_scroll(-1*self._tmwm, 'units') elif event.num == 5: # down on Linux self.top.f.canvas.yview_scroll(1*self._tmwm, 'units') else: # assume event.delta has the direction, but reversed sign self.top.f.canvas.yview_scroll(-(event.delta)*self._tmwm, 'units') # A bug appeared in Python 2.3 that caused tk_focusNext and # tk_focusPrev to fail. The follwoing two routines now will # trap this error and call "fixed" versions of these tk routines # instead in the event of such errors. def focusNext(self, event): """Set focus to next item in sequence""" try: event.widget.tk_focusNext().focus_set() except TypeError: # see tkinter equivalent code for tk_focusNext to see # commented original version name = event.widget.tk.call('tk_focusNext', event.widget._w) event.widget._nametowidget(str(name)).focus_set() def focusPrev(self, event): """Set focus to previous item in sequence""" try: event.widget.tk_focusPrev().focus_set() except TypeError: # see tkinter equivalent code for tk_focusPrev to see # commented original version name = event.widget.tk.call('tk_focusPrev', event.widget._w) event.widget._nametowidget(str(name)).focus_set() def doScroll(self, event): """Scroll the panel down to ensure widget with focus to be visible Tracks the last widget that doScroll was called for and ignores repeated calls. That handles the case where the focus moves not between parameter entries but to someplace outside the hierarchy. In that case the scrolling is not expected. Returns false if the scroll is ignored, else true. """ canvas = self.top.f.canvas widgetWithFocus = event.widget if widgetWithFocus is self.lastFocusWidget: return FALSE self.lastFocusWidget = widgetWithFocus if widgetWithFocus is None: return TRUE # determine distance of widget from top & bottom edges of canvas y1 = widgetWithFocus.winfo_rooty() y2 = y1 + widgetWithFocus.winfo_height() cy1 = canvas.winfo_rooty() cy2 = cy1 + canvas.winfo_height() yinc = self.yscrollincrement if y1<cy1: # this will continue to work when integer division goes away sdist = int((y1-cy1-yinc+1.)/yinc) canvas.yview_scroll(sdist, "units") elif cy2<y2: sdist = int((y2-cy2+yinc-1.)/yinc) canvas.yview_scroll(sdist, "units") return TRUE def _handleParListMismatch(self, probStr, extra=False): """ Handle the situation where two par lists do not match. This is meant to allow subclasses to override. Note that this only handles "missing" pars and "extra" pars, not wrong-type pars. """ errmsg = 'ERROR: mismatch between default and current par lists ' + \ 'for task "'+self.taskName+'"' if probStr: errmsg += '\n\t'+probStr errmsg += '\n(try: "unlearn '+self.taskName+'")' print(errmsg) return False def _setupDefaultParamList(self): """ This creates self.defaultParamList. It also does some checks on the paramList, sets its order if needed, and deletes any extra or unknown pars if found. We assume the order of self.defaultParamList is the correct order. """ # Obtain the default parameter list self.defaultParamList = self._taskParsObj.getDefaultParList() theParamList = self._taskParsObj.getParList() # Lengths are probably equal but this isn't necessarily an error # here, so we check for differences below. if len(self.defaultParamList) != len(theParamList): # whoa, lengths don't match (could be some missing or some extra) pmsg = 'Current list not same length as default list' if not self._handleParListMismatch(pmsg): return False # convert current par values to a dict of { par-fullname:par-object } # for use below ourpardict = {} for par in theParamList: ourpardict[par.fullName()] = par # Sort our paramList according to the order of the defaultParamList # and repopulate the list according to that order. Create sortednames. sortednames = [p.fullName() for p in self.defaultParamList] # Rebuild par list sorted into correct order. Also find/flag any # missing pars or any extra/unknown pars. This automatically deletes # "extras" by not adding them to the sorted list in the first place. migrated = [] newList = [] for fullName in sortednames: if fullName in ourpardict: newList.append(ourpardict[fullName]) migrated.append(fullName) # make sure all get moved over else: # this is a missing par - insert the default version theDfltVer = \ [p for p in self.defaultParamList if p.fullName()==fullName] newList.append(copy.deepcopy(theDfltVer[0])) # Update! Next line writes to the self._taskParsObj.getParList() obj theParamList[:] = newList # fill with newList, keep same mem pointer # See if any got left out extras = [fn for fn in ourpardict if not fn in migrated] for fullName in extras: # this is an extra/unknown par - let subclass handle it if not self._handleParListMismatch('Unexpected par: "'+\ fullName+'"', extra=True): return False print('Ignoring unexpected par: "'+p+'"') # return value indicates that all is well to continue return True # Method to create the parameter entries def makeEntries(self, master, statusBar): # Get model data, the list of pars theParamList = self._taskParsObj.getParList() # Determine the size of the longest input string inputLength = INPUTWIDTH for i in range(self.numParams): inputString = theParamList[i].name if len(inputString) > inputLength: inputLength = len(inputString) # Set up the field widths # Allow extra spaces for buffer and in case the longest parameter # has the hidden parameter indicator self.fieldWidths = {} self.fieldWidths['inputWidth'] = inputLength + 4 self.fieldWidths['valueWidth'] = VALUEWIDTH self.fieldWidths['promptWidth'] = PROMPTWIDTH # Loop over the parameters to create the entries self.entryNo = [None] * self.numParams dfltsVerb = self._defaultsButtonTitle if dfltsVerb[-1]=='s': dfltsVerb = dfltsVerb[:-1] for i in range(self.numParams): scope = theParamList[i].scope eparOpt = self._nonStandardEparOptionFor(theParamList[i].type) cbo = self._defineEditedCallbackObjectFor(scope, theParamList[i].name) hcbo = None if self._knowTaskHelpIsHtml: hcbo = self self.entryNo[i] = eparoption.eparOptionFactory(master, statusBar, theParamList[i], self.defaultParamList[i], self.doScroll, self.fieldWidths, plugIn=eparOpt, editedCallbackObj=cbo, helpCallbackObj=hcbo, mainGuiObj=self, defaultsVerb=dfltsVerb, bg=self._entsColor, indent = scope not in (None, '', '.'), flagging = self._flagNonDefaultVals, flaggedColor=self._flagColor) def _nonStandardEparOptionFor(self, paramTypeStr): """ Hook to allow subclasses to employ their own GUI option type. Return None or a class which derives from EparOption. """ return None def _defineEditedCallbackObjectFor(self, parScope, parName): """ Hook to allow subclasses to set their own callback-containing object to be used when a given option/parameter is edited. See notes in EparOption. """ return None def _isUnpackagedTask(self): """ Hook to allow subclasses to state that this is a rogue task, not affiliated with a specific package, affecting its display. """ return self.pkgName is None or len(self.pkgName) < 1 def _toggleSectionActiveState(self, sectionName, state, skipList): """ Make an entire section (minus skipList items) either active or inactive. sectionName is the same as the param's scope. """ # Get model data, the list of pars theParamList = self._taskParsObj.getParList() # Loop over their assoc. entries for i in range(self.numParams): if theParamList[i].scope == sectionName: if skipList and theParamList[i].name in skipList: # self.entryNo[i].setActiveState(True) # these always active pass # if it started active, we don't need to reactivate it else: self.entryNo[i].setActiveState(state) # Method to print the package and task names and to set up the menu # button for the choice of the display for the task help page def printNames(self, top, taskName, pkgName): topbox = Frame(top, bg=self._taskColor) textbox = Frame(topbox, bg=self._taskColor) # helpbox = Frame(topbox, bg=self._taskColor) # Set up the information strings if self._isUnpackagedTask(): # label for a parameter list is just filename packString = " "+self._unpackagedTaskTitle+" = "+taskName Label(textbox, text=packString, bg=self._taskColor).pack(side=TOP, anchor=W) else: # labels for task packString = " Package = " + pkgName.upper() Label(textbox, text=packString, bg=self._taskColor).pack(side=TOP, anchor=W) taskString = " Task = " + taskName.upper() Label(textbox, text=taskString, bg=self._taskColor).pack(side=TOP, anchor=W) textbox.pack(side=LEFT, anchor=W) topbox.pack(side=TOP, expand=FALSE, fill=X) # Method to set up the parent menu bar def makeMenuBar(self, top): menubar = Frame(top, bd=1, relief=GROOVE, bg=self._frmeColor) # Generate the menus fileMenu = self.makeFileMenu(menubar) if self._showOpenButton(): openMenu = self.makeOpenMenu(menubar) # When redesigned, optionsMenu should only be on the parent #if not self.isChild: # optionsMenu = self.makeOptionsMenu(menubar) optionsMenu = self.makeOptionsMenu(menubar) helpMenu = self.makeHelpMenu(menubar) menubar.pack(fill=X) # Method to generate a "File" menu def makeFileMenu(self, menubar): fileButton = Menubutton(menubar, text='File', bg=self._frmeColor) fileButton.pack(side=LEFT, padx=2) fileButton.menu = Menu(fileButton, tearoff=0) # fileButton.menu.add_command(label="Open...", command=self.pfopen) if self._showExecuteButton: fileButton.menu.add_command(label="Execute", command=self.execute) if self.isChild: fileButton.menu.entryconfigure(0, state=DISABLED) saqlbl ="Save" if self._useSimpleAutoClose: saqlbl += " & Quit" fileButton.menu.add_command(label=saqlbl, command=self.saveAndClose) if not self.isChild: fileButton.menu.add_command(label="Save As...", command=self.saveAs) fileButton.menu.add_separator() fileButton.menu.add_command(label=self._defaultsButtonTitle, command=self.unlearn) fileButton.menu.add_separator() if not self._useSimpleAutoClose: fileButton.menu.add_command(label="Close", command=self.closeGui) fileButton.menu.add_command(label="Cancel", command=self.abort) # Associate the menu with the menu button fileButton["menu"] = fileButton.menu return fileButton def _updateOpen(self): # Get new data flist = self._getOpenChoices() # Delete old choices if self._numOpenMenuItems > 0: self._openMenu.delete(0, self._numOpenMenuItems-1) # Add all new choices self._numOpenMenuItems = len(flist) if self._numOpenMenuItems > 0: for ff in flist: if ff[-3:] == '...': self._openMenu.add_separator() self._numOpenMenuItems += 1 self._openMenu.add_radiobutton(label=ff, command=self.pfopen, variable=self._openMenuChoice, indicatoron=0) # value=ff) ... (same as label) self._openMenuChoice.set(0) # so nothing has check mark next to it else: showwarning(title="No Files To Open", message="No extra "+ \ 'parameter files found for task "'+self.taskName+'".') def _getOpenChoices(self): """ Get the current list of file name choices for the Open button. This is meant for subclasses to override. """ return [] # Method to generate an "Open" menu def makeOpenMenu(self, menubar): self._openMenuChoice = StringVar() # this is used till GUI closes self._numOpenMenuItems = 1 # see dummy openBtn = Menubutton(menubar, text='Open...', bg=self._frmeColor) openBtn.bind("<Enter>", self.printOpenInfo) openBtn.pack(side=LEFT, padx=2) openBtn.menu = Menu(openBtn, tearoff=0, postcommand=self._updateOpen) openBtn.menu.bind("<Enter>", self.printOpenInfo) openBtn.menu.add_radiobutton(label=' ', # dummy, no command variable=self._openMenuChoice) # value=fname ... (same as label) if self.isChild: openBtn.menu.entryconfigure(0, state=DISABLED) # Associate the menu with the menu button openBtn["menu"] = openBtn.menu # Keep a ref for ourselves self._openMenu = openBtn.menu return openBtn # Method to generate the "Options" menu for the parent EPAR only def makeOptionsMenu(self, menubar): # Set up the menu for the various choices they have self._helpChoice = StringVar() if self._showHelpInBrowser: self._helpChoice.set("BROWSER") else: self._helpChoice.set("WINDOW") if self._showSaveCloseOnExec: self._execChoice = IntVar() self._execChoice.set(int(self._saveAndCloseOnExec)) optionButton = Menubutton(menubar, text="Options", bg=self._frmeColor) optionButton.pack(side=LEFT, padx=2) optionButton.menu = Menu(optionButton, tearoff=0) optionButton.menu.add_radiobutton(label="Display Task Help in a Window", value="WINDOW", command=self.setHelpType, variable=self._helpChoice) optionButton.menu.add_radiobutton(label="Display Task Help in a Browser", value="BROWSER", command=self.setHelpType, variable=self._helpChoice) if self._showExecuteButton and self._showSaveCloseOnExec: optionButton.menu.add_separator() optionButton.menu.add_checkbutton(label="Save and Close on Execute", command=self.setExecOpt, variable=self._execChoice) if self._showFlaggingChoice: self._flagChoice = IntVar() self._flagChoice.set(int(self._flagNonDefaultVals)) optionButton.menu.add_separator() optionButton.menu.add_checkbutton(label="Flag Non-default Values", command=self.setFlagOpt, variable=self._flagChoice) # Associate the menu with the menu button optionButton["menu"] = optionButton.menu return optionButton def capTaskName(self): """ Return task name with first letter capitalized. """ return self.taskName[:1].upper() + self.taskName[1:] def makeHelpMenu(self, menubar): button = Menubutton(menubar, text='Help', bg=self._frmeColor) button.bind("<Enter>", self.printHelpInfo) button.pack(side=RIGHT, padx=2) button.menu = Menu(button, tearoff=0) button.menu.bind("<Enter>", self.printHelpInfo) button.menu.add_command(label=self.capTaskName()+" Help", command=self.showTaskHelp) button.menu.add_command(label=self._appName+" Help", command=self.eparHelp) button.menu.add_separator() button.menu.add_command(label='Show '+self._appName+' Log', command=self.showLogHist) button["menu"] = button.menu return button # Method to set up the action buttons # Create the buttons in an order for good navigation def buttonBox(self, top): box = Frame(top, bg=self._bboxColor, bd=1, relief=SUNKEN) # When the Button is exited, the information clears, and the # Button goes back to the nonactive color. top.bind("<Leave>", self.clearInfo) # Execute the task if self._showExecuteButton: buttonExecute = Button(box, text="Execute", bg=self._bboxColor, relief=RAISED, command=self.execute, highlightbackground=self._bboxColor) buttonExecute.pack(side=LEFT, padx=5, pady=7) buttonExecute.bind("<Enter>", self.printExecuteInfo) if not self._useSimpleAutoClose: # separate this button from the others - it's unusual strut = Label(box, text="", bg=self._bboxColor) strut.pack(side=LEFT, padx=20) # EXECUTE button is disabled for child windows if self.isChild: buttonExecute.configure(state=DISABLED) # Save the parameter settings and exit from epar saqlbl ="Save" if self._useSimpleAutoClose: saqlbl += " & Quit" btn = Button(box, text=saqlbl, relief=RAISED, command=self.saveAndClose, bg=self._bboxColor, highlightbackground=self._bboxColor) btn.pack(side=LEFT, padx=5, pady=7) btn.bind("<Enter>", self.printSaveQuitInfo) # Unlearn all the parameter settings (set back to the defaults) buttonUnlearn = Button(box, text=self._defaultsButtonTitle, relief=RAISED, command=self.unlearn, bg=self._bboxColor, highlightbackground=self._bboxColor) if self._showExtraHelpButton: buttonUnlearn.pack(side=LEFT, padx=5, pady=7) else: buttonUnlearn.pack(side=RIGHT, padx=5, pady=7) buttonUnlearn.bind("<Enter>", self.printUnlearnInfo) # Buttons to close versus abort this edit session. if not self._useSimpleAutoClose: buttonClose = Button(box, text="Close", relief=RAISED, command=self.closeGui, bg=self._bboxColor, highlightbackground=self._bboxColor) buttonClose.pack(side=LEFT, padx=5, pady=7) buttonClose.bind("<Enter>", self.printCloseInfo) buttonAbort = Button(box, text="Cancel", bg=self._bboxColor, relief=RAISED, command=self.abort, highlightbackground=self._bboxColor) buttonAbort.pack(side=LEFT, padx=5, pady=7) buttonAbort.bind("<Enter>", self.printAbortInfo) # Generate the Help button if self._showExtraHelpButton: buttonHelp = Button(box, text=self.capTaskName()+" Help", relief=RAISED, command=self.showTaskHelp, bg=self._bboxColor, highlightbackground=self._bboxColor) buttonHelp.pack(side=RIGHT, padx=5, pady=7) buttonHelp.bind("<Enter>", self.printHelpInfo) # Pack box.pack(fill=X, expand=FALSE) def setExecOpt(self, event=None): self._saveAndCloseOnExec = bool(self._execChoice.get()) def setFlagOpt(self, event=None): self._flagNonDefaultVals = bool(self._flagChoice.get()) for entry in self.entryNo: entry.setIsFlagging(self._flagNonDefaultVals, True) def setHelpType(self, event=None): """ Determine which method of displaying the help pages was chosen by the user. WINDOW displays in a task generated scrollable window. BROWSER invokes the task's HTML help pages and displays in a browser. """ self._showHelpInBrowser = bool(self._helpChoice.get() == "BROWSER") def eparHelp(self, event=None): self._showAnyHelp('epar') def showTaskHelp(self, event=None): self._showAnyHelp('task') def showParamHelp(self, parName): self._showAnyHelp('task', tag=parName) def showLogHist(self, event=None): self._showAnyHelp('log') # # Define flyover help text associated with the action buttons # def clearInfo(self, event): self.showStatus("") def printHelpInfo(self, event): self.showStatus("Display the help page", cat=TIP) def printUnlearnInfo(self, event): self.showStatus("Set all parameter values to their default settings", cat=TIP) def printSaveQuitInfo(self, event): if self._useSimpleAutoClose: self.showStatus("Save current entries and exit this edit session", cat=TIP) else: self.showStatus("Save the current entries to "+ \ self._taskParsObj.getFilename(), cat=TIP) def printOpenInfo(self, event): self.showStatus( "Load and edit parameter values from a user-specified file", cat=TIP) def printCloseInfo(self, event): self.showStatus("Close this edit session. Save first?", cat=TIP) def printAbortInfo(self, event): self.showStatus( "Abort this edit session, discarding any unsaved changes.",cat=TIP) def printExecuteInfo(self, event): if self._saveAndCloseOnExec: self.showStatus( "Execute the task, and save and exit this edit session", cat=TIP) else: self.showStatus("Execute the task; this window will remain open", cat=TIP) # Process invalid input values and invoke a query dialog def processBadEntries(self, badEntriesList, taskname, canCancel=True): badEntriesString = "Task " + taskname.upper() + " --\n" \ "Invalid values have been entered.\n\n" \ "Parameter Bad Value Reset Value\n" for i in range (len(badEntriesList)): badEntriesString = badEntriesString + \ "%15s %10s %10s\n" % (badEntriesList[i][0], \ badEntriesList[i][1], badEntriesList[i][2]) if canCancel: badEntriesString += '\n"OK" to continue using'+ \ ' the reset values, or "Cancel" to re-enter values?\n' else: badEntriesString += \ "\n All invalid values will return to their 'Reset Value'.\n" # Invoke the modal message dialog if canCancel: return askokcancel("Notice", badEntriesString) else: return showwarning("Notice", badEntriesString) def hasUnsavedChanges(self): """ Determine if there are any edits in the GUI that have not yet been saved (e.g. to a file). This needs to be overridden by a subclass. In the meantime, just default (on the safe side) to everything being ready-to-save. """ return True def closeGui(self, event=None): self.saveAndClose(askBeforeSave=True, forceClose=True) # SAVE/QUIT: save the parameter settings and exit epar def saveAndClose(self, event=None, askBeforeSave=False, forceClose=False): # First, see if we can/should skip the save doTheSave = True if askBeforeSave: if self.hasUnsavedChanges(): doTheSave = askyesno('Save?', 'Save before closing?') else: # no unsaved changes, so no need to save OR even to prompt doTheSave = False # no need to save OR prompt # first save the child parameters, aborting save if # invalid entries were encountered if doTheSave and self.checkSetSaveChildren(): return # Save all the entries and verify them, keeping track of the # invalid entries which have been reset to their original input values self.badEntriesList = None if doTheSave: self.badEntriesList = self.checkSetSaveEntries() # Note, there is a BUG here - if they hit Cancel, the save to # file has occurred anyway (they may not care) - need to refactor. # If there were invalid entries, prepare the message dialog if self.badEntriesList: ansOKCANCEL = self.processBadEntries(self.badEntriesList, self.taskName) if not ansOKCANCEL: return # If there were no invalid entries or the user says OK, continue... # Save any GUI settings we care about. This is a good time to do so # even if the window isn't closing, but especially if it is. self._saveGuiSettings() # Done saving. Only close the window if we are running in that mode. if not (self._useSimpleAutoClose or forceClose): return # Remove the main epar window self.top.focus_set() self.top.withdraw() # If not a child window, quit the entire session if not self.isChild: self.top.destroy() self.top.quit() # Declare the global variables so they can be updated global CHILDX global CHILDY # Reset to the start location CHILDX = PARENTX CHILDY = PARENTY # OPEN: load parameter settings from a user-specified file def pfopen(self, event=None): """ Load the parameter settings from a user-specified file. Any epar changes here should be coordinated with the corresponding tpar pfopen function. """ raise NotImplementedError("EditParDialog is not to be used directly") def _getSaveAsFilter(self): """ Return a string to be used as the filter arg to the save file dialog during Save-As. Override for more specific behavior. """ return "*.*" def _saveAsPreSave_Hook(self, fnameToBeUsed): """ Allow a subclass any specific checks right before the save. """ return None def _saveAsPostSave_Hook(self, fnameToBeUsed): """ Allow a subclass any specific checks right after the save. """ return None # SAVE AS: save the parameter settings to a user-specified file def saveAs(self, event=None): """ Save the parameter settings to a user-specified file. Any changes here must be coordinated with the corresponding tpar save_as function. """ self.debug('Clicked Save as...') # On Linux Pers..Dlg causes the cwd to change, so get a copy of current curdir = os.getcwd() # The user wishes to save to a different name writeProtChoice = self._writeProtectOnSaveAs if capable.OF_TKFD_IN_EPAR: # Prompt using native looking dialog fname = asksaveasfilename(parent=self.top, title='Save Parameter File As', defaultextension=self._defSaveAsExt, initialdir=os.path.dirname(self._getSaveAsFilter())) else: # Prompt. (could use tkinter's FileDialog, but this one is prettier) # initWProtState is only used in the 1st call of a session from . import filedlg fd = filedlg.PersistSaveFileDialog(self.top, "Save Parameter File As", self._getSaveAsFilter(), initWProtState=writeProtChoice) if fd.Show() != 1: fd.DialogCleanup() os.chdir(curdir) # in case file dlg moved us return fname = fd.GetFileName() writeProtChoice = fd.GetWriteProtectChoice() fd.DialogCleanup() if not fname: return # canceled # First check the child parameters, aborting save if # invalid entries were encountered if self.checkSetSaveChildren(): os.chdir(curdir) # in case file dlg moved us return # Run any subclass-specific steps right before the save self._saveAsPreSave_Hook(fname) # Verify all the entries (without save), keeping track of the invalid # entries which have been reset to their original input values self.badEntriesList = self.checkSetSaveEntries(doSave=False) # If there were invalid entries, prepare the message dialog if self.badEntriesList: ansOKCANCEL = self.processBadEntries(self.badEntriesList, self.taskName) if not ansOKCANCEL: os.chdir(curdir) # in case file dlg moved us return # If there were no invalid entries or the user says OK, finally # save to their stated file. Since we have already processed the # bad entries, there should be none returned. mstr = "TASKMETA: task="+self.taskName+" package="+self.pkgName if self.checkSetSaveEntries(doSave=True, filename=fname, comment=mstr, set_ro=writeProtChoice, overwriteRO=True): os.chdir(curdir) # in case file dlg moved us raise Exception("Unexpected bad entries for: "+self.taskName) # Run any subclass-specific steps right after the save self._saveAsPostSave_Hook(fname) os.chdir(curdir) # in case file dlg moved us # EXECUTE: save the parameter settings and run the task def execute(self, event=None): self.debug('Clicked Execute') # first save the child parameters, aborting save if # invalid entries were encountered if self.checkSetSaveChildren(): return # If we are only executing (no save and close) do so here and return if not self._saveAndCloseOnExec: # First check the parameter values self.badEntriesList = self.checkSetSaveEntries(doSave=False) # If there were invalid entries, show the message dialog if self.badEntriesList: ansOKCANCEL = self.processBadEntries(self.badEntriesList, self.taskName) if not ansOKCANCEL: return self.showStatus("Task "+self.taskName+" is running...", keep=2) self._executed = True # note for later use self.runTask() return # Now save the parameter values of the parent self.badEntriesList = self.checkSetSaveEntries() # If there were invalid entries in the parent epar dialog, prepare # the message dialog if self.badEntriesList: ansOKCANCEL = self.processBadEntries(self.badEntriesList, self.taskName) if not ansOKCANCEL: return # If there were no invalid entries or the user said OK # Save any GUI settings we care about since window is closing self._saveGuiSettings() # Remove the main epar window self.top.focus_set() self.top.withdraw() self.top.destroy() print("\nTask "+self.taskName+" is running...\n") # Run the task try: self._executed = True # note for later use self.runTask() finally: self.top.quit() # Declare the global variables so they can be updated global CHILDX global CHILDY # Reset to the start location CHILDX = PARENTX CHILDY = PARENTY # ABORT: abort this epar session def abort(self, event=None): # Declare the global variables so they can be updated global CHILDX global CHILDY # Reset to the start location CHILDX = PARENTX CHILDY = PARENTY # Give focus back to parent window and abort self.top.focus_set() self.top.withdraw() self._canceled = True # note for later use # Do not destroy the window, just hide it for now. # This is so EXECUTE will not get an error - properly use Mediator. #self.top.destroy() if not self.isChild: self.top.destroy() self.top.quit() # UNLEARN: unlearn all the parameters by setting their values # back to the system default def unlearn(self, event=None): self.debug('Clicked Unlearn') # Reset the values of the parameters self.unlearnAllEntries(self.top.f.canvas.entries) self.freshenFocus() # HTMLHELP: invoke the HTML help def htmlHelp(self, helpString=None, title=None, istask=False, tag=None): """ Pop up the help in a browser window. By default, this tries to show the help for the current task. With the option arguments, it can be used to show any help string. """ # Check the help string. If it turns out to be a URL, launch that, # if not, dump it to a quick and dirty tmp html file to make it # presentable, and pass that file name as the URL. if not helpString: helpString = self.getHelpString(self.pkgName+'.'+self.taskName) if not title: title = self.taskName lwr = helpString.lower() if lwr.startswith("http:") or lwr.startswith("https:") or \ lwr.startswith("file:"): url = helpString if tag and url.find('#') < 0: url += '#'+tag # print('LAUNCHING: '+url) # DBG irafutils.launchBrowser(url, subj=title) else: # Write it to a temp HTML file to display (fd, fname) = tempfile.mkstemp(suffix='.html', prefix='editpar_') os.close(fd) f = open(fname, 'w') if istask and self._knowTaskHelpIsHtml: f.write(helpString) else: f.write('<html><head><title>'+title+'\n') f.write('

'+title+'

\n') f.write('
\n'+helpString+'\n
') f.close() irafutils.launchBrowser("file://"+fname, subj=title) def _showAnyHelp(self, kind, tag=None): """ Invoke task/epar/etc. help and put the page in a window. This same logic is used for GUI help, task help, log msgs, etc. """ # sanity check if kind not in ('epar', 'task', 'log'): raise ValueError('Unknown help kind: ' + str(kind)) #----------------------------------------- # See if they'd like to view in a browser #----------------------------------------- if self._showHelpInBrowser or (kind == 'task' and self._knowTaskHelpIsHtml): if kind == 'epar': self.htmlHelp(helpString=self._appHelpString, title='Parameter Editor Help') if kind == 'task': self.htmlHelp(istask=True, tag=tag) if kind == 'log': self.htmlHelp(helpString='\n'.join(self._msgHistory), title=self._appName+' Event Log') return #----------------------------------------- # Now try to pop up the regular Tk window #----------------------------------------- wins = {'epar':self.eparHelpWin, 'task':self.irafHelpWin, 'log': self.logHistWin, } window = wins[kind] try: if window.state() != NORMAL: window.deiconify() window.tkraise() return except (AttributeError, TclError): pass #--------------------------------------------------------- # That didn't succeed (window is still None), so build it #--------------------------------------------------------- if kind == 'epar': self.eparHelpWin = self.makeHelpWin(self._appHelpString, title='Parameter Editor Help') if kind == 'task': # Acquire the task help as a string # Need to include the package name for the task to # avoid name conflicts with tasks from other packages. WJH self.irafHelpWin = self.makeHelpWin(self.getHelpString( self.pkgName+'.'+self.taskName)) if kind == 'log': self.logHistWin = self.makeHelpWin('\n'.join(self._msgHistory), title=self._appName+' Event Log') def canceled(self): """ Did the user click Cancel? (or close us via the window manager) """ return self._canceled def executed(self): """ Did the user click Execute? """ return self._executed # Get the task help in a string def getHelpString(self, taskname): """ Provide a task-specific help string. """ return self._taskParsObj.getHelpAsString() # Set up the help dialog (browser) def makeHelpWin(self, helpString, title="Parameter Editor Help Browser"): # Generate a new Toplevel window for the browser # hb = Toplevel(self.top, bg="SlateGray3") hb = Toplevel(self.top, bg=None) hb.title(title) hb.iconLabel = title # Set up the Menu Bar hb.menubar = Frame(hb, relief=RIDGE, borderwidth=0) hb.menubar.button = Button(hb.menubar, text="Close", relief=RAISED, command=hb.destroy) hb.menubar.button.pack() hb.menubar.pack(side=BOTTOM, padx=5, pady=5) # Define the Frame for the scrolling Listbox hb.frame = Frame(hb, relief=RIDGE, borderwidth=1) # Attach a vertical Scrollbar to the Frame hb.frame.vscroll = Scrollbar(hb.frame, orient=VERTICAL, width=11, relief=SUNKEN, activerelief=RAISED, takefocus=FALSE) # Define the Listbox and setup the Scrollbar hb.frame.list = Listbox(hb.frame, relief=FLAT, height=25, width=80, takefocus=FALSE, selectmode=SINGLE, selectborderwidth=0) hb.frame.list['yscrollcommand'] = hb.frame.vscroll.set hb.frame.vscroll['command'] = hb.frame.list.yview hb.frame.vscroll.pack(side=RIGHT, fill=Y) hb.frame.list.pack(side=TOP, expand=TRUE, fill=BOTH) hb.frame.pack(side=TOP, fill=BOTH, expand=TRUE) # Insert each line of the helpString onto the Frame listing = helpString.split('\n') for line in listing: # Filter the text *** DO THIS A BETTER WAY *** line = line.replace("\x0e", "") line = line.replace("\x0f", "") line = line.replace("\f", "") # Insert the text into the Listbox hb.frame.list.insert(END, line) # When the Listbox appears, the listing will be at the beginning y = hb.frame.vscroll.get()[0] hb.frame.list.yview(int(y)) # enable Page Up/Down keys scroll = hb.frame.list.yview_scroll hb.bind('', lambda event, fs=scroll: fs(1, "pages")) hb.bind('', lambda event, fs=scroll: fs(-1, "pages")) # Position this dialog relative to the parent hb.geometry("+%d+%d" % (self.top.winfo_rootx() + HELPX, self.top.winfo_rooty() + HELPY)) return hb def validate(self): return 1 def setAllEntriesFromParList(self, aParList, updateModel=False): """ Set all the parameter entry values in the GUI to the values in the given par list. If 'updateModel' is True, the internal param list will be updated to the new values as well as the GUI entries (slower and not always necessary). Note the corresponding TparDisplay method. """ # Get model data, the list of pars theParamList = self._taskParsObj.getParList() # we may modify members if len(aParList) != len(theParamList): showwarning(message="Attempting to set parameter values from a "+ \ "list of different length ("+str(len(aParList))+ \ ") than the number shown here ("+ \ str(len(theParamList))+"). Be aware.", title="Parameter List Length Mismatch") # LOOP THRU GUI PAR LIST for i in range(self.numParams): par = theParamList[i] if par.type == "pset": continue # skip PSET's for now gui_entry = self.entryNo[i] # Set the value in the paramList before setting it in the GUI # This may be in the form of a list, or an IrafParList (getValue) if isinstance(aParList, list): # Since "aParList" can have them in different order and number # than we do, we'll have to first find the matching param. found = False for newpar in aParList: if newpar.name==par.name and newpar.scope==par.scope: par.set(newpar.value) # same as .get(native=1,prompt=0) found = True break # Now see if newpar was found in our list if not found: pnm = par.name if len(par.scope): pnm = par.scope+'.'+par.name raise UnfoundParamError('Error - Unfound Parameter! \n\n'+\ 'Expected parameter "'+pnm+'" for task "'+ \ self.taskName+'". \nThere may be others...') else: # assume has getValue() par.set(aParList.getValue(par.name, native=1, prompt=0)) # gui holds a str, but par.value is native; conversion occurs gui_entry.forceValue(par.value, noteEdited=False) # no triggers yet if updateModel: # Update the model values via checkSetSaveEntries self.badEntriesList = self.checkSetSaveEntries(doSave=False) # If there were invalid entries, prepare the message dialog if self.badEntriesList: self.processBadEntries(self.badEntriesList, self.taskName, canCancel=False) def unlearnAllEntries(self, master): """ Method to "unlearn" all the parameter entry values in the GUI and set the parameter back to the default value """ for entry in self.entryNo: entry.unlearnValue() def getValue(self, name, scope=None, native=False): """ Return current par value from the GUI. This does not do any validation, and it it not necessarily the same value saved in the model, which is always behind the GUI setting, in time. This is NOT to be used to get all the values - it would not be efficient. """ # Get model data, the list of pars theParamList = self._taskParsObj.getParList() # NOTE: If par scope is given, it will be used, otherwise it is # assumed to be unneeded and the first name-match is returned. fullName = basicpar.makeFullName(scope, name) # Loop over the parameters to find the requested par for i in range(self.numParams): par = theParamList[i] # IrafPar or subclass entry = self.entryNo[i] # EparOption or subclass if par.fullName() == fullName or \ (scope is None and par.name == name): if native: return entry.convertToNative(entry.choice.get()) else: return entry.choice.get() # We didn't find the requested par raise RuntimeError('Could not find par: "'+fullName+'"') # Read, save, and validate the entries def checkSetSaveEntries(self, doSave=True, filename=None, comment=None, fleeOnBadVals=False, allowGuiChanges=True, set_ro=False, overwriteRO=False): self.badEntries = [] asNative = self._taskParsObj.knowAsNative() # Get model data, the list of pars theParamList = self._taskParsObj.getParList() # Loop over the parameters to obtain the modified information for i in range(self.numParams): par = theParamList[i] # IrafPar or subclass entry = self.entryNo[i] # EparOption or subclass # Cannot change an entry if it is a PSET, just skip if par.type == "pset": continue # get current state of par in the gui value = entry.choice.get() # Set new values for changed parameters - a bit tricky, # since changes that weren't followed by a return or # tab have not yet been checked. If we eventually # use a widget that can check all changes, we will # only need to check the isChanged flag. if par.isChanged() or value != entry.previousValue: # CHECK: Verify the value. If its invalid (and allowGuiChanges), # the value will be converted to its original valid value. # Maintain a list of the reset values for user notification. # Always call entryCheck, no matter what type of _taskParsObj, # since entryCheck can do some basic type checking. failed = False if entry.entryCheck(repair=allowGuiChanges): failed = True self.badEntries.append([entry.name, value, entry.choice.get()]) if fleeOnBadVals: return self.badEntries # See if we need to do a more serious validity check elif self._taskParsObj.canPerformValidation(): # if we are planning to save in native type, test that way if asNative: try: value = entry.convertToNative(value) except: failed = True prev = entry.previousValue self.badEntries.append([entry.name, value, prev]) if fleeOnBadVals: return self.badEntries if allowGuiChanges: entry.choice.set(prev) # now try the val in it's validator if not failed: valOK, prev = self._taskParsObj.tryValue(entry.name, value, scope=par.scope) if not valOK: failed = True self.badEntries.append([entry.name,str(value),prev]) if fleeOnBadVals: return self.badEntries if allowGuiChanges: entry.choice.set(prev) # get value again in case it changed - this version IS valid value = entry.choice.get() if asNative: value = entry.convertToNative(value) # SET: Update the task parameter (also does the conversion # from string) self._taskParsObj.setParam(par.name, value, scope=par.scope, check=0, idxHint=i) # SAVE: Save results to the given file if doSave: self.debug('Saving...') out = self._doActualSave(filename, comment, set_ro=set_ro, overwriteRO=overwriteRO) if len(out): self.showStatus(out, keep=2) # inform user on saves return self.badEntries def _doActualSave(self, fname, comment, set_ro=False): """ Here we call the method on the _taskParsObj to do the actual save. Return a string result to be printed to the screen. """ # do something like # return self._taskParsObj.saveParList(filename=fname, comment=comment) raise NotImplementedError("EditParDialog is not to be used directly") def checkSetSaveChildren(self, doSave=True): """Check, then set, then save the parameter settings for all child (pset) windows. Prompts if any problems are found. Returns None on success, list of bad entries on failure. """ if self.isChild: return # Need to get all the entries and verify them. # Save the children in backwards order to coincide with the # display of the dialogs (LIFO) for n in range (len(self.top.childList)-1, -1, -1): self.badEntriesList = self.top.childList[n]. \ checkSetSaveEntries(doSave=doSave) if self.badEntriesList: ansOKCANCEL = self.processBadEntries(self.badEntriesList, self.top.childList[n].taskName) if not ansOKCANCEL: return self.badEntriesList # If there were no invalid entries or the user says OK, # close down the child and increment to the next child self.top.childList[n].top.focus_set() self.top.childList[n].top.withdraw() del self.top.childList[n] # all windows saved successfully return def _pushMessages(self): """ Internal callback used to make sure the msg list keeps moving. """ # This continues to get itself called until no msgs are left in list. self.showStatus('') if len(self._statusMsgsToShow) > 0: self.top.after(200, self._pushMessages) def debug(self, msg): """ Convenience function. Use showStatus without puting into GUI. """ self.showStatus(msg, cat=DBG) def showStatus(self, msg, keep=0, cat=None): """ Show the given status string, but not until any given delay from the previous message has expired. keep is a time (secs) to force the message to remain without being overwritten or cleared. cat is a string category used only in the historical log. """ # prep it, space-wise msg = msg.strip() if len(msg) > 0: # right here is the ideal place to collect a history of messages forhist = msg if cat: forhist = '['+cat+'] '+msg forhist = time.strftime("%a %H:%M:%S")+': '+forhist self._msgHistory.append(forhist) # now set the spacing msg = ' '+msg # stop here if it is a category not shown in the GUI if cat == DBG: return # see if we can show it now = time.time() if now >= self._leaveStatusMsgUntil: # we are clear, can show a msg # first see if this msg is '' - if so we will show an important # waiting msg instead of the '', and then pop it off our list if len(msg) < 1 and len(self._statusMsgsToShow) > 0: msg, keep = self._statusMsgsToShow[0] # overwrite both args del self._statusMsgsToShow[0] # now actuall print the status out to the status widget self.top.status.config(text = msg) # reset our delay flag self._leaveStatusMsgUntil = 0 if keep > 0: self._leaveStatusMsgUntil = now + keep else: # there is a previous message still up, is this one important? if len(msg) > 0 and keep > 0: # Uh-oh, this is an important message that we don't want to # simply skip, but on the other hand we can't show it yet... # So we add it to _statusMsgsToShow and show it later (asap) if (msg,keep) not in self._statusMsgsToShow: if len(self._statusMsgsToShow) < 7: self._statusMsgsToShow.append( (msg,keep) ) # tuple # kick off timer loop to get this one pushed through if len(self._statusMsgsToShow) == 1: self._pushMessages() else: # should never happen, but just in case print("Lost message!: "+msg+" (too far behind...)") # Run the task def runTask(self): # Use the run method of the IrafTask class # Set mode='h' so it does not prompt for parameters (like IRAF epar) # Also turn on parameter saving try: self._taskParsObj.run(mode='h', _save=1) except taskpars.NoExecError as nee: # catch only this, let all else thru showwarning(message="No way found to run task\n\n"+\ str(nee), title="Can Not Run Task") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/eparoption.py0000644000175000017500000011102414214070451016373 0ustar00olesoles"""eparoption.py: module for defining the various parameter display options to be used for the parameter editor task. The widget that is used for entering the parameter value is the variant. Instances should be created using the eparOptionFactory function defined at the end of the module. Parameter types: string - Entry widget *gcur - NOT IMPLEMENTED AT THIS TIME ukey - NOT IMPLEMENTED AT THIS TIME pset - Action button real - Entry widget int - Entry widget boolean - Radiobutton widget array real - NOT IMPLEMENTED AT THIS TIME array int - NOT IMPLEMENTED AT THIS TIME Enumerated lists - Menubutton/Menu widget $Id$ M.D. De La Pena, 1999 August 05 """ # System level modules import sys import string from . import capable if capable.OF_GRAPHICS: from tkinter import * from tkinter.filedialog import askdirectory, askopenfilename else: StringVar = None # Are we using X? (see description of logic in pyraf's wutil.py) USING_X = True if sys.platform == 'darwin': junk = ",".join(sys.path) USING_X = junk.lower().find('/pyobjc') < 0 del junk # Constants MAXLIST = 15 MAXLINES = 100 XSHIFT = 110 DSCRPTN_FLAG = ' (***)' class EparOption: """EparOption base class Implementation for a specific parameter type must implement the makeInputWidget method and must create an attribute `entry' with the base widget created. The entry widget is used for focus setting and automatic scrolling. doScroll is a callback to do the scrolling when tab changes focus. """ # Chosen option choiceClass = StringVar def __init__(self, master, statusBar, paramInfo, defaultParamInfo, doScroll, fieldWidths, defaultsVerb, bg, indent=False, helpCallbackObj=None, mainGuiObj=None): # Connect to the information/status Label self.status = statusBar # Hook to allow scroll when this widget gets focus self.doScroll = doScroll # Track selection at the last FocusOut event self.lastSelection = (0,END) # A new Frame is created for each parameter entry self.master = master self.bkgColor = bg self.master_frame = Frame(self.master, bg=self.bkgColor) self.paramInfo = paramInfo self.defaultParamInfo = defaultParamInfo self.defaultsVerb = defaultsVerb self.inputWidth = fieldWidths.get('inputWidth') self.valueWidth = fieldWidths.get('valueWidth') self.promptWidth = fieldWidths.get('promptWidth') self.choice = self.choiceClass(self.master_frame) self.name = self.paramInfo.name self.value = self.paramInfo.get(field = "p_filename", native = 0, prompt = 0) self.previousValue = self.value self._editedCallbackObj = None self._helpCallbackObj = helpCallbackObj self._mainGuiObj = mainGuiObj self._lastWidgetEditedVal = None self._flagNonDefaultVals = False self._flaggedColor = "red" # DISABLE any indent for now - not sure why but this causes odd text # field sizes in other (unrelated and unindented) parameters... Maybe # because it messes with the total width of the window... if 0 and indent: self.spacer = Label(self.master_frame, anchor=W, takefocus=0, text="", width=3, bg=self.bkgColor) self.spacer.pack(side=LEFT, fill=X, expand=TRUE) # Generate the input label if self.paramInfo.get(field = "p_mode") == "h": self.inputLabel = Label(self.master_frame, anchor = W, text = "("+self.getShowName()+")", width = self.inputWidth, bg=self.bkgColor) else: self.inputLabel = Label(self.master_frame, anchor = W, text = self.getShowName(), width = self.inputWidth, bg=self.bkgColor) self.inputLabel.pack(side = LEFT, fill = X, expand = TRUE) # Get the prompt string and determine if special handling is needed # Use the prompt/description from the default version, in case they # have edited theirs - this is not editable - see ticket #803 self.prompt = self.defaultParamInfo.get(field="p_prompt", native=0, prompt=0) # Check the prompt to determine how many lines of valid text exist lines = self.prompt.split("\n") nlines = len(lines) promptLines = " " + lines[0] infoLines = "" blankLineNo = MAXLINES if (nlines > 1): # Keep all the lines of text before the blank line for the prompt for i in range(1, nlines): ntokens = lines[i].split() if ntokens != []: promptLines = "\n".join([promptLines, lines[i]]) else: blankLineNo = i break self._flagged = False if promptLines.endswith(DSCRPTN_FLAG): promptLines = promptLines[:-len(DSCRPTN_FLAG)] self._flagged = True fgColor = "black" # turn off this red coloring for the DSCRPTN_FLAG - see #803 # if self._flagged: fgColor = "red" # Generate the prompt label self.promptLabel = Label(self.master_frame, anchor=W, fg=fgColor, text=promptLines, width=self.promptWidth, bg=self.bkgColor) self.promptLabel.pack(side=RIGHT, fill=X, expand=TRUE) # Settings for subclasses to override in the makeInputWidget method self.isSelectable = True # ie widget has text (num or str) to select # Default is none of items on popup menu are activated # These can be changed by the makeInputWidget method to customize # behavior for each widget. self.browserEnabled = DISABLED self.clearEnabled = DISABLED self.unlearnEnabled = DISABLED self.helpEnabled = DISABLED if self._helpCallbackObj is not None: self.helpEnabled = NORMAL # Generate the input widget depending upon the datatype self.makeInputWidget() # print(self.name, self.__class__) # DBG line self.entry.bind('', self.focusOut, "+") self.entry.bind('', self.focusIn, "+") # Trap keys that leave field and validate entry self.entry.bind('', self.entryCheck, "+") self.entry.bind('', self.entryCheck, "+") self.entry.bind('', self.entryCheck, "+") self.entry.bind('', self.entryCheck, "+") self.entry.bind('', self.entryCheck, "+") self.entry.bind('', self.entryCheck, "+") try: # special shift-tab binding needed for (some? all?) linux systems self.entry.bind('', self.entryCheck, "+") except TclError: # Ignore exception here, the binding can't be relevant # if ISO_Left_Tab is unknown. pass # Bind the right button to a popup menu of choices if USING_X: self.entry.bind('', self.popupChoices) else: self.entry.bind('', self.popupChoices) # Pack the parameter entry Frame self.master_frame.pack(side=TOP, fill=X, ipady=1) # If there is more text associated with this entry, join all the # lines of text with the blank line. This is the "special" text # information. if (blankLineNo < (nlines - 1)): # Put the text after the blank line into its own Frame self.master.infoText = Frame(self.master) for j in range(blankLineNo + 1, nlines): ntokens = lines[j].split() if ntokens != []: infoLines = "\n".join([infoLines, lines[j]]) else: break # Assign the informational text to the label and pack self.master.infoText.label = Label(self.master.infoText, text = infoLines, anchor = W, bg = self.bkgColor) self.master.infoText.label.pack(side = LEFT) self.master.infoText.pack(side = TOP, anchor = W) def setFlaggedColor(self, colorstr): self._flaggedColor = colorstr def setIsFlagging(self, isFlagging, redrawImmediately): self._flagNonDefaultVals = isFlagging if redrawImmediately: if self._flagNonDefaultVals: curVal = self.choice.get() else: # otheriwse we don't care; use None; is ok and faster curVal = None self.flagThisPar(curVal, True) def getShowName(self): """ Return the name to be shown in the GUI for this par/option. """ return self.name def extraBindingsForSelectableText(self): """ Collect in 1 place the bindings needed for watchTextSelection() """ # See notes in watchTextSelection self.entry.bind('', self.watchTextSelection, "+") self.entry.bind('', self.watchTextSelection, "+") self.entry.bind('', self.watchTextSelection, "+") self.entry.bind('', self.watchTextSelection, "+") self.entry.bind('', self.watchTextSelection, "+") self.entry.bind('', self.watchTextSelection, "+") def convertToNative(self, aVal): """ The basic type is natively a string. """ return None if aVal is None else str(aVal) def focusOut(self, event=None): """Clear selection (if text is selected in this widget)""" # do nothing if this isn't a text-enabled widget if not self.isSelectable: return if self.entryCheck(event) is None: # Entry value is OK # Save the last selection so it can be restored if we # come right back to this widget. Then clear the selection # before moving on. entry = self.entry try: if not entry.selection_present(): self.lastSelection = None else: self.lastSelection = (entry.index(SEL_FIRST), entry.index(SEL_LAST)) except AttributeError: pass if USING_X and sys.platform == 'darwin': pass # do nothing here - we need it left selected for cut/paste else: entry.selection_clear() else: return "break" def watchTextSelection(self, event=None): """ Callback used to see if there is a new text selection. In certain cases we manually add the text to the clipboard (though on most platforms the correct behavior happens automatically). """ # Note that this isn't perfect - it is a key click behind when # selections are made via shift-arrow. If this becomes important, it # can likely be fixed with after(). if self.entry.selection_present(): # entry must be text entry type i1 = self.entry.index(SEL_FIRST) i2 = self.entry.index(SEL_LAST) if i1 >= 0 and i2 >= 0 and i2 > i1: sel = self.entry.get()[i1:i2] # Add to clipboard on platforms where necessary. print('selected: "'+sel+'"') # The following is unneeded if the selected text stays selected # when focus is lost or another app is bought to the forground. # if sel and USING_X and sys.platform == 'darwin': # clipboard_helper.put(sel, 'PRIMARY') def focusIn(self, event=None): """Select all text (if applicable) on taking focus""" try: # doScroll returns false if the call was ignored because the # last call also came from this widget. That avoids unwanted # scrolls and text selection when the focus moves in and out # of the window. if self.doScroll(event): self.entry.selection_range(0, END) # select all text in widget else: # restore selection to what it was on the last FocusOut if self.lastSelection: self.entry.selection_range(*self.lastSelection) except AttributeError: pass # Check the validity of the entry # If valid, changes the value of the parameter (note that this # is a copy, so change is not permanent until save) # Parameter change also sets the isChanged flag. def entryCheck(self, event=None, repair=True): # Make sure the input is legal value = self.choice.get() try: if value != self.previousValue: # THIS will likely get into IrafPar's _coerceOneValue() self.paramInfo.set(value) # fire any applicable triggers, whether value has changed or not self.widgetEdited(action='entry') return None except ValueError as exceptionInfo: # Reset the entry to the previous (presumably valid) value if repair: self.choice.set(self.previousValue) self.status.bell() errorMsg = str(exceptionInfo) if event is not None: self.status.config(text = errorMsg) # highlight the text again and terminate processing so # focus stays in this widget self.focusIn(event) return "break" def widgetEdited(self, event=None, val=None, action='entry', skipDups=True): """ A general method for firing any applicable triggers when a value has been set. This is meant to be easily callable from any part of this class (or its subclasses), so that it can be called as soon as need be (immed. on click?). This is smart enough to be called multiple times, itself handling the removal of any/all duplicate successive calls (unless skipDups is False). If val is None, it will use the GUI entry's current value via choice.get(). See teal.py for a description of action. """ # be as lightweight as possible if obj doesn't care about this stuff if not self._editedCallbackObj and not self._flagNonDefaultVals: return # get the current value curVal = val # take this first, if it is given if curVal is None: curVal = self.choice.get() # do any flagging self.flagThisPar(curVal, False) # see if this is a duplicate successive call for the same value if skipDups and curVal==self._lastWidgetEditedVal: return # pull trigger if not self._editedCallbackObj: return self._editedCallbackObj.edited(self.paramInfo.scope, self.paramInfo.name, self.previousValue, curVal, action) # for our duplicate checker self._lastWidgetEditedVal = curVal def focus_set(self, event=None): """Set focus to input widget""" self.entry.focus_set() # Generate the the input widget as appropriate to the parameter datatype def makeInputWidget(self): pass def popupChoices(self, event=None): """Popup right-click menu of special parameter operations Relies on browserEnabled, clearEnabled, unlearnEnabled, helpEnabled instance attributes to determine which items are available. """ # don't bother if all items are disabled if NORMAL not in (self.browserEnabled, self.clearEnabled, self.unlearnEnabled, self.helpEnabled): return self.menu = Menu(self.entry, tearoff = 0) if self.browserEnabled != DISABLED: # Handle file and directory in different functions (tkFileDialog) if capable.OF_TKFD_IN_EPAR: self.menu.add_command(label = "File Browser", state = self.browserEnabled, command = self.fileBrowser) self.menu.add_command(label = "Directory Browser", state = self.browserEnabled, command = self.dirBrowser) # Handle file and directory in the same function (filedlg) else: self.menu.add_command(label = "File/Directory Browser", state = self.browserEnabled, command = self.fileBrowser) self.menu.add_separator() self.menu.add_command(label = "Clear", state = self.clearEnabled, command = self.clearEntry) self.menu.add_command(label = self.defaultsVerb, state = self.unlearnEnabled, command = self.unlearnValue) self.menu.add_command(label = 'Help', state = self.helpEnabled, command = self.helpOnParam) # Get the current y-coordinate of the Entry ycoord = self.entry.winfo_rooty() # Get the current x-coordinate of the cursor xcoord = self.entry.winfo_pointerx() - XSHIFT # Display the Menu as a popup as it is not associated with a Button self.menu.tk_popup(xcoord, ycoord) def fileBrowser(self): """Invoke a tkinter file dialog""" if capable.OF_TKFD_IN_EPAR: fname = askopenfilename(parent=self.entry, title="Select File") else: from . import filedlg self.fd = filedlg.PersistLoadFileDialog(self.entry, "Select File", "*") if self.fd.Show() != 1: self.fd.DialogCleanup() return fname = self.fd.GetFileName() self.fd.DialogCleanup() if not fname: return # canceled self.choice.set(fname) # don't select when we go back to widget to reduce risk of # accidentally typing over the filename self.lastSelection = None def dirBrowser(self): """Invoke a tkinter directory dialog""" if capable.OF_TKFD_IN_EPAR: fname = askdirectory(parent=self.entry, title="Select Directory") else: raise NotImplementedError('Fix popupChoices() logic.') if not fname: return # canceled self.choice.set(fname) # don't select when we go back to widget to reduce risk of # accidentally typing over the filename self.lastSelection = None def clearEntry(self): """Clear just this Entry""" self.entry.delete(0, END) def forceValue(self, newVal, noteEdited=False): """Force-set a parameter entry to the given value""" if newVal is None: newVal = "" self.choice.set(newVal) if noteEdited: self.widgetEdited(val=newVal, skipDups=False) # WARNING: the value of noteEdited really should be false (default) # in most cases because we need the widgetEdited calls to be arranged # at one level higher than we are (single param). We need to allow the # caller to first loop over all eparoptions, setting their values # without triggering anything, and THEN go through again and run any # triggers. def unlearnValue(self): """Unlearn a parameter value by setting it back to its default""" defaultValue = self.defaultParamInfo.get(field = "p_filename", native = 0, prompt = 0) self.choice.set(defaultValue) def helpOnParam(self): """ Try to display help specific to this parameter. """ if self._helpCallbackObj is not None: self._helpCallbackObj.showParamHelp(self.name) def setEditedCallbackObj(self, ecbo): """ Sets a callback object to be triggred when this option/parameter is edited. The object is expected to have an "edited()" method which takes args as shown where it is called in widgetEdited. """ self._editedCallbackObj = ecbo def setActiveState(self, active): """ Use this to enable or disable (grey out) a parameter. """ st = DISABLED if active: st = NORMAL self.entry.configure(state=st) self.inputLabel.configure(state=st) self.promptLabel.configure(state=st) def flagThisPar(self, currentVal, force): """ If this par's value is different from the default value, it is here that we flag it somehow as such. This basic version simply makes the surrounding text red (or returns it to normal). May be overridden. Leave force at False if you want to allow this mehtod to make smart time-saving decisions about when it can skip recoloring because it is already the right color. Set force to true if you think we got out of sync and need to be fixed. """ # Get out ASAP if we can if (not force) and (not self._flagNonDefaultVals): return # handle simple case before comparing values (quick return) if force and not self._flagNonDefaultVals: self._flagged = False self.promptLabel.configure(fg="black") return # Get/format values to compare currentNative = self.convertToNative(currentVal) defaultNative = self.convertToNative(self.defaultParamInfo.value) # par.value is same as par.get(native=1,prompt=0) # flag or unflag as needed if currentNative != defaultNative: if not self._flagged or force: self._flagged = True self.promptLabel.configure(fg=self._flaggedColor) # was red else: # same as def if self._flagged or force: self._flagged = False self.promptLabel.configure(fg="black") # ['red','blue','green','purple','yellow','orange','black'] class EnumEparOption(EparOption): def makeInputWidget(self): self.unlearnEnabled = NORMAL self.isSelectable = False # Set the initial value for the button self.choice.set(self.value) # Need to adjust the value width so the menu button is # aligned properly if USING_X: self.valueWidth = self.valueWidth - 4 else: pass # self.valueWidth = self.valueWidth - 0 # looks right on Aqua # Generate the button self.entry = Menubutton(self.master_frame, width = self.valueWidth, text = self.choice.get(), # label relief = RAISED, anchor = W, # alignment textvariable = self.choice, # var to sync indicatoron = 1, takefocus = 1, highlightthickness = 1, activeforeground='black', fg='black', bg=self.bkgColor) self.entry.menu = Menu(self.entry, tearoff=0, postcommand=self.postcmd, fg = 'black', bg=self.bkgColor) # Generate the dictionary of shortcuts using first letter, # second if first not available, etc. self.shortcuts = {} trylist = self.paramInfo.choice underline = {} charset = string.ascii_lowercase + string.digits i = 0 while trylist: trylist2 = [] for option in trylist: # shortcuts dictionary is case-insensitive letter = option[i:i+1].lower() if letter in self.shortcuts: # will try again with next letter trylist2.append(option) elif letter: if letter in charset: self.shortcuts[letter] = option self.shortcuts[letter.upper()] = option underline[option] = i else: # only allow letters, numbers to be shortcuts # keep going in case this is an embedded blank (e.g.) trylist2.append(option) else: # no letters left, so no shortcut for this item underline[option] = -1 trylist = trylist2 i = i+1 # Generate the menu options with shortcuts underlined for option in self.paramInfo.choice: lbl = option if lbl=='-': lbl = ' -' # Tk treats '-' as a separator request self.entry.menu.add_radiobutton(label = lbl, value = option, variable = self.choice, command = self.selected, indicatoron = 0, underline = underline[option]) # set up a pointer from the menubutton back to the menu self.entry['menu'] = self.entry.menu self.entry.pack(side = LEFT) # shortcut keys jump to items for letter in self.shortcuts: self.entry.bind('<%s>' % letter, self.keypress) # Left button sets focus (as well as popping up menu) self.entry.bind('', self.focus_set) def keypress(self, event): """Allow keys typed in widget to select items""" try: self.choice.set(self.shortcuts[event.keysym]) except KeyError: # key not found (probably a bug, since we intend to catch # only events from shortcut keys, but ignore it anyway) pass def postcmd(self): """Make sure proper entry is activated when menu is posted""" value = self.choice.get() try: index = self.paramInfo.choice.index(value) self.entry.menu.activate(index) except ValueError: # initial null value may not be in list pass def selected(self): """They have chosen an enumerated option.""" self.widgetEdited(action='entry') # kick off any checks that need doin # def setActiveState(self, active): # [...] # for i in range(len(self.paramInfo.choice)): # this doesn't seem to # self.entry.menu.entryconfig(i, state=st) # make the menu text grey # [...] class BooleanEparOption(EparOption): def convertToNative(self, aVal): """ Convert to native bool; interpret certain strings. """ if aVal is None: return None if isinstance(aVal, bool): return aVal # otherwise interpret strings return str(aVal).lower() in ('1','on','yes','true') def makeInputWidget(self): self.unlearnEnabled = NORMAL self.isSelectable = False # Need to buffer the value width so the radio buttons and # the adjoining labels are aligned properly self.valueWidth = self.valueWidth + 10 if USING_X: self.padWidth = (self.valueWidth // 2) + 5 # looks right else: self.padWidth = 2 # looks right on Aqua # boolean parameters have 3 values: yes, no & undefined # Just display two choices (but variable may initially be # undefined) self.choice.set(self.value) self.entry = Frame(self.master_frame, relief = FLAT, width = self.valueWidth, takefocus = 1, highlightthickness = 1, bg=self.bkgColor, highlightbackground=self.bkgColor) if not USING_X: spacerL= Label(self.entry, takefocus=0, text="", width=2, bg=self.bkgColor) spacerL.pack(side=LEFT, fill=X, expand=TRUE) self.rbyes = Radiobutton(self.entry, text = "Yes", variable = self.choice, value = "yes", anchor = W, takefocus = 0, underline = 0, bg = self.bkgColor, highlightbackground=self.bkgColor) self.rbyes.pack(side=LEFT, ipadx=self.padWidth) if not USING_X: spacerM= Label(self.entry, takefocus=0, text="", width=3, bg=self.bkgColor) spacerM.pack(side=LEFT, fill=X, expand=TRUE) spacerR = Label(self.entry, takefocus=0, text="", width=2, bg=self.bkgColor) spacerR.pack(side=RIGHT, fill=X, expand=TRUE) self.rbno = Radiobutton(self.entry, text = "No", variable = self.choice, value = "no", anchor = W, takefocus = 0, underline = 0, bg = self.bkgColor, highlightbackground=self.bkgColor) self.rbno.pack(side = RIGHT, ipadx = self.padWidth) self.entry.pack(side = LEFT) # keyboard accelerators # Y/y sets yes, N/n sets no, space toggles selection self.entry.bind('', self.set) self.entry.bind('', self.set) self.entry.bind('', self.unset) self.entry.bind('', self.unset) self.entry.bind('', self.toggle) # When variable changes, make sure widget gets focus self.choice.trace("w", self.trace) # Right-click menu is bound to individual widgets too if USING_X: self.rbno.bind('', self.popupChoices) self.rbyes.bind('', self.popupChoices) else: self.rbno.bind('', self.popupChoices) self.rbyes.bind('', self.popupChoices) spacerM.bind('', self.popupChoices) # Regular selection - allow immediate trigger/check self.rbyes.bind('', self.boolWidgetEditedYes) self.rbno.bind('', self.boolWidgetEditedNo) def trace(self, *args): self.entry.focus_set() # Only needed over widgetEdited because the Yes isn't set yet def boolWidgetEditedYes(self, event=None): self.widgetEdited(val="yes") # Only needed over widgetEdited because the No isn't set yet def boolWidgetEditedNo(self, event=None): self.widgetEdited(val="no") def set(self, event=None): """Set value to Yes""" self.rbyes.select() self.widgetEdited() def unset(self, event=None): """Set value to No""" self.rbno.select() self.widgetEdited() def toggle(self, event=None): """Toggle value between Yes and No""" if self.choice.get() == "yes": self.rbno.select() else: self.rbyes.select() self.widgetEdited() def setActiveState(self, active): st = DISABLED if active: st = NORMAL self.rbyes.configure(state=st) self.rbno.configure(state=st) self.inputLabel.configure(state=st) self.promptLabel.configure(state=st) class StringEparOption(EparOption): def makeInputWidget(self): self.browserEnabled = NORMAL self.clearEnabled = NORMAL self.unlearnEnabled = NORMAL self.choice.set(self.value) self.entry = Entry(self.master_frame, width = self.valueWidth, textvariable = self.choice) # , bg=self.bkgColor) self.entry.pack(side = LEFT, fill = X, expand = TRUE) # self.extraBindingsForSelectableText() # do not use yet class ActionEparButton(EparOption): def getButtonLabel(self): return self.value def makeInputWidget(self): # self.choice.set(self.value) self.browserEnabled = DISABLED self.clearEnabled = DISABLED self.unlearnEnabled = DISABLED self.helpEnabled = NORMAL # Need to adjust the value width so the button is aligned properly if USING_X: self.valueWidth = self.valueWidth - 3 else: self.valueWidth = self.valueWidth - 2 self.isSelectable = False # Generate the button self.entry = Button(self.master_frame, width = self.valueWidth, text = self.getButtonLabel(), relief = RAISED, background = self.bkgColor, highlightbackground = self.bkgColor, command = self.clicked) self.entry.pack(side = LEFT) def clicked(self): raise NotImplementedError('clicked() must be implemented') def unlearnValue(self): pass # widget class that works for numbers and arrays of numbers class NumberEparOption(EparOption): def convertToNative(self, aVal): """ Natively as an int. """ if aVal in (None, '', 'None', 'NONE', 'INDEF'): return None return int(aVal) def notNull(self, value): vsplit = value.split() return vsplit.count("INDEF") != len(vsplit) def makeInputWidget(self): self.browserEnabled = DISABLED self.clearEnabled = NORMAL self.unlearnEnabled = NORMAL # Retain the original parameter value in case of bad entry self.previousValue = self.value self.choice.set(self.value) self.entry = Entry(self.master_frame, width = self.valueWidth, textvariable = self.choice) #, bg=self.bkgColor) self.entry.pack(side = LEFT) # self.extraBindingsForSelectableText() # do not use yet # Check the validity of the entry # Note that doing this using the parameter set method automatically # checks max, min, special value (INDEF, parameter indirection), etc. def entryCheck(self, event = None, repair = True): """ Ensure any INDEF entry is uppercase, before base class behavior """ valupr = self.choice.get().upper() if valupr.strip() == 'INDEF': self.choice.set(valupr) return EparOption.entryCheck(self, event, repair = repair) # numeric widget class specific to floats class FloatEparOption(NumberEparOption): def convertToNative(self, aVal): """ Natively as a float. """ if aVal in (None, '', 'None', 'NONE', 'INDEF'): return None return float(aVal) # EparOption values for non-string types _eparOptionDict = { "b": BooleanEparOption, "r": FloatEparOption, "R": FloatEparOption, "d": FloatEparOption, "I": NumberEparOption, "i": NumberEparOption, "z": ActionEparButton, "ar": FloatEparOption, "ai": NumberEparOption, } def eparOptionFactory(master, statusBar, param, defaultParam, doScroll, fieldWidths, plugIn=None, editedCallbackObj=None, helpCallbackObj=None, mainGuiObj=None, defaultsVerb="Default", bg=None, indent=False, flagging=False, flaggedColor=None): """Return EparOption item of appropriate type for the parameter param""" # Allow passed-in overrides if plugIn is not None: eparOption = plugIn # If there is an enumerated list, regardless of datatype use EnumEparOption elif param.choice is not None: eparOption = EnumEparOption else: # Use String for types not in the dictionary eparOption = _eparOptionDict.get(param.type, StringEparOption) # Create it eo = eparOption(master, statusBar, param, defaultParam, doScroll, fieldWidths, defaultsVerb, bg, indent=indent, helpCallbackObj=helpCallbackObj, mainGuiObj=mainGuiObj) eo.setEditedCallbackObj(editedCallbackObj) eo.setIsFlagging(flagging, False) if flaggedColor: eo.setFlaggedColor(flaggedColor) return eo ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/filedlg.py0000644000175000017500000003660114214070451015630 0ustar00olesoles#### # Class FileDialog # # Purpose # ------- # # FileDialog's are widgets that allow one to select file names by # clicking on file names, directory names, filters, etc. # # Standard Usage # -------------- # # F = FileDialog(widget, some_title, some_filter) # if F.Show() != 1: # F.DialogCleanup() # return # file_name = F.GetFileName() # F.DialogCleanup() #### """ $Id$ """ import os from . import capable from subprocess import getoutput # nosec if capable.OF_GRAPHICS: import tkinter as TKNTR from . import alert from .dialog import * # noqa else: ModalDialog = object class FileDialog(ModalDialog): # constructor lastWrtPrtChoice = None def __init__(self, widget, title, filter="*", initWProtState=None): """ Supply parent widget, title, filter, and initWProtState (True or False). Set initWProtState to None to hide the write-protect check-box. """ self.widget = widget self.filter = filter.strip() self.orig_dir = os.getcwd() self.cwd = os.getcwd() # the logical current working directory self.showChmod = initWProtState is not None # normally we use persistence for lastWrtPrtChoice; use this 1st time if FileDialog.lastWrtPrtChoice is None: FileDialog.lastWrtPrtChoice = initWProtState # Allow a start-directory as part of the given filter if self.filter.find(os.sep) >= 0: self.cwd = os.path.dirname(self.filter) self.filter = os.path.basename(self.filter) # do this second! # main Dialog code Dialog.__init__(self, widget) # setup routine called back from Dialog def SetupDialog(self): # directory label self.dirFrame = Frame(self.top) self.dirFrame['relief'] = 'raised' self.dirFrame['bd'] = '2' self.dirFrame.pack({'expand':'no', 'side':'top', 'fill':'both'}) self.dirLabel = Label(self.dirFrame) self.dirLabel["text"] = "Directory:" self.dirLabel.pack({'expand':'no', 'side':'left', 'fill':'none'}) # editable filter self.filterFrame = Frame(self.top) self.filterFrame['relief'] = 'raised' self.filterFrame['bd'] = '2' self.filterFrame.pack({'expand':'no', 'side':'top', 'fill':'both'}) self.filterLabel = Label(self.filterFrame) self.filterLabel["text"] = "Filter:" self.filterLabel.pack({'expand':'no', 'side':'left', 'fill':'none'}) self.filterEntry = Entry(self.filterFrame) self.filterEntry.bind('', self.FilterReturnKey) self.filterEntry["width"] = "40" self.filterEntry["relief"] = "ridge" self.filterEntry.pack({'expand':'yes', 'side':'right', 'fill':'x'}) self.filterEntry.insert(0, self.filter) # the directory and file listboxes self.listBoxFrame = Frame(self.top) self.listBoxFrame['relief'] = 'raised' self.listBoxFrame['bd'] = '2' self.listBoxFrame.pack({'expand':'yes', 'side' :'top', 'pady' :'2', 'padx': '0', 'fill' :'both'}) self.CreateDirListBox() self.CreateFileListBox() self.UpdateListBoxes() # write-protect option junk = FileDialog.lastWrtPrtChoice if junk is None: junk = 0 self.wpVar = IntVar(value=junk) # use class attr if self.showChmod: self.writeProtFrame = Frame(self.top) self.writeProtFrame['relief'] = 'raised' self.writeProtFrame['bd'] = '2' self.writeProtFrame.pack({'expand':'no','side':'top','fill':'both'}) self.wpButton = Checkbutton(self.writeProtFrame, text="Write-protect after save", command=self.wrtPrtClick, var=self.wpVar) self.wpButton.pack({'expand':'no', 'side':'left'}) # editable filename self.fileNameFrame = Frame(self.top) self.fileNameFrame.pack({'expand':'no', 'side':'top', 'fill':'both'}) self.fileNameFrame['relief'] = 'raised' self.fileNameFrame['bd'] = '2' self.fileNameLabel = Label(self.fileNameFrame) self.fileNameLabel["text"] = "File:" self.fileNameLabel.pack({'expand':'no', 'side':'left', 'fill':'none'}) self.fileNameEntry = Entry(self.fileNameFrame) self.fileNameEntry["width"] = "40" self.fileNameEntry["relief"] = "ridge" self.fileNameEntry.pack({'expand':'yes', 'side':'right', 'fill':'x', 'pady': '2'}) self.fileNameEntry.bind('', self.FileNameReturnKey) # buttons - ok, filter, cancel self.buttonFrame = Frame(self.top) self.buttonFrame['relief'] = 'raised' self.buttonFrame['bd'] = '2' self.buttonFrame.pack({'expand':'no', 'side':'top', 'fill':'x'}) self.okButton = Button(self.buttonFrame) self.okButton["text"] = "OK" self.okButton["command"] = self.OkPressed self.okButton["width"] = 8 self.okButton.pack({'expand':'yes', 'pady':'2', 'side':'left'}) self.filterButton = Button(self.buttonFrame) self.filterButton["text"] = "Filter" self.filterButton["command"] = self.FilterPressed self.filterButton["width"] = 8 self.filterButton.pack({'expand':'yes', 'pady':'2', 'side':'left'}) button = Button(self.buttonFrame) button["text"] = "Cancel" button["command"] = self.CancelPressed button["width"] = 8 button.pack({'expand':'yes', 'pady':'2', 'side':'left'}) # create the directory list box def CreateDirListBox(self): frame = Frame(self.listBoxFrame) frame.pack({'expand':'yes', 'side' :'left', 'pady' :'1', 'fill' :'both'}) frame['relief'] = 'raised' frame['bd'] = '2' filesFrame = Frame(frame) filesFrame['relief'] = 'flat' filesFrame['bd'] = '2' filesFrame.pack({'side':'top', 'expand':'no', 'fill':'x'}) label = Label(filesFrame) label['text'] = 'Directories:' label.pack({'side':'left', 'expand':'yes', 'anchor':'w', 'fill':'none'}) scrollBar = Scrollbar(frame, {'orient':'vertical'}) scrollBar.pack({'expand':'no', 'side':'right', 'fill':'y'}) self.dirLb = Listbox(frame, {'yscroll':scrollBar.set}) self.dirLb.pack({'expand':'yes', 'side' :'top', 'pady' :'1', 'fill' :'both'}) self.dirLb.bind('<1>', self.DoSelection) self.dirLb.bind('', self.DoDoubleClickDir) scrollBar['command'] = self.dirLb.yview # create the files list box def CreateFileListBox(self): frame = Frame(self.listBoxFrame) frame['relief'] = 'raised' frame['bd'] = '2' frame.pack({'expand':'yes', 'side' :'left', 'pady' :'1', 'padx' :'1', 'fill' :'both'}) filesFrame = Frame(frame) filesFrame['relief'] = 'flat' filesFrame['bd'] = '2' filesFrame.pack({'side':'top', 'expand':'no', 'fill':'x'}) label = Label(filesFrame) label['text'] = 'Files:' label.pack({'side':'left', 'expand':'yes', 'anchor':'w', 'fill':'none'}) scrollBar = Scrollbar(frame, {'orient':'vertical'}) scrollBar.pack({'side':'right', 'fill':'y'}) self.fileLb = Listbox(frame, {'yscroll':scrollBar.set}) self.fileLb.pack({'expand':'yes', 'side' :'top', 'pady' :'0', 'fill' :'both'}) self.fileLb.bind('<1>', self.DoSelection) self.fileLb.bind('', self.DoDoubleClickFile) scrollBar['command'] = self.fileLb.yview # update the listboxes and directory label after a change of directory def UpdateListBoxes(self): cwd = self.cwd self.fileLb.delete(0, self.fileLb.size()) filter = self.filterEntry.get() # '*' will list recursively, we don't want that. if filter == '*': filter = '' cmd = "/bin/ls " + os.path.join(cwd, filter) cmdOutput = getoutput(cmd) # nosec files = cmdOutput.split("\n") files.sort() for i in range(len(files)): if os.path.isfile(os.path.join(cwd, files[i])): self.fileLb.insert('end', os.path.basename(files[i])) self.dirLb.delete(0, self.dirLb.size()) files = os.listdir(cwd) if cwd != '/': files.append('..') files.sort() for i in range(len(files)): if os.path.isdir(os.path.join(cwd, files[i])): self.dirLb.insert('end', files[i]) self.dirLabel['text'] = "Directory:" + self.cwd_print() # selection handlers def DoSelection(self, event): lb = event.widget field = self.fileNameEntry field.delete(0, AtEnd()) field.insert(0, os.path.join(self.cwd_print(), lb.get(lb.nearest(event.y)))) if TKNTR.TkVersion >= 4.0: lb.select_clear(0, "end") lb.select_anchor(lb.nearest(event.y)) else: lb.select_clear() lb.select_from(lb.nearest(event.y)) def DoDoubleClickDir(self, event): lb = event.widget self.cwd = os.path.join(self.cwd, lb.get(lb.nearest(event.y))) self.UpdateListBoxes() def DoDoubleClickFile(self, event): self.OkPressed() def OkPressed(self): self.TerminateDialog(1) def wrtPrtClick(self): FileDialog.lastWrtPrtChoice = self.wpVar.get() # update class attr def FileNameReturnKey(self, event): # if its a relative path then include the cwd in the name name = self.fileNameEntry.get().strip() if not os.path.isabs(os.path.expanduser(name)): self.fileNameEntry.delete(0, 'end') self.fileNameEntry.insert(0, os.path.join(self.cwd_print(), name)) self.okButton.flash() self.OkPressed() def FilterReturnKey(self, event): filter = self.filterEntry.get().strip() self.filterEntry.delete(0, 'end') self.filterEntry.insert(0, filter) self.filterButton.flash() self.UpdateListBoxes() def FilterPressed(self): self.UpdateListBoxes() def CancelPressed(self): self.TerminateDialog(0) def GetFileName(self): return self.fileNameEntry.get() def GetWriteProtectChoice(self): return bool(self.wpVar.get()) # return the logical current working directory in a printable form # ie. without all the X/.. pairs. The easiest way to do this is to # chdir to cwd and get the path there. def cwd_print(self): os.chdir(self.cwd) p = os.getcwd() os.chdir(self.orig_dir) return p #### # Class LoadFileDialog # # Purpose # ------- # # Specialisation of FileDialog for loading files. #### class LoadFileDialog(FileDialog): def __init__(self, master, title, filter): FileDialog.__init__(self, master, title, filter) self.top.title(title) def OkPressed(self): fileName = self.GetFileName() if os.path.exists(fileName) == 0: msg = 'File ' + fileName + ' not found.' errorDlg = alert.ErrorDialog(self.top, msg) errorDlg.Show() errorDlg.DialogCleanup() return FileDialog.OkPressed(self) #### # Class SaveFileDialog # # Purpose # ------- # # Specialisation of FileDialog for saving files. #### class SaveFileDialog(FileDialog): def __init__(self, master, title, filter): FileDialog.__init__(self, master, title, filter) self.top.title(title) def OkPressed(self): fileName = self.GetFileName() if os.path.exists(fileName) == 1: msg = 'File ' + fileName + ' exists.\nDo you wish to overwrite it?' warningDlg = alert.WarningDialog(self.top, msg) if warningDlg.Show() == 0: warningDlg.DialogCleanup() return warningDlg.DialogCleanup() FileDialog.OkPressed(self) #---------------------------------------------------------------------------- ############################################################################# # # Class: PersistFileDialog # Purpose: Essentially the same as FileDialog, except this class contains # a class variable (lastAccessedDir) which keeps track of the last # directory from which a file was chosen. Subsequent invocations of # this dialog in the same Python session will start up in the last # directory where a file was successfully chosen, rather than in the # current working directory. # # History: M.D. De La Pena, 08 June 2000 # ############################################################################# class PersistFileDialog(FileDialog): # Define a class variable to track the last accessed directory lastAccessedDir = None def __init__(self, widget, title, filter="*", initWProtState=None): FileDialog.__init__(self, widget, title, filter, initWProtState) # If the last accessed directory were not None, start up # the file browser in the last accessed directory. if self.__class__.lastAccessedDir: self.cwd = self.__class__.lastAccessedDir # Override the OkPressed method from the parent in order to # update the class variable. def OkPressed(self): self.__class__.lastAccessedDir = self.cwd_print() self.TerminateDialog(1) ############################################################################# # # Class: PersistLoadFileDialog # Purpose: Essentially the same as LoadFileDialog, except this class invokes # PersistFileDialog instead of FileDialog. # # History: M.D. De La Pena, 08 June 2000 # ############################################################################# class PersistLoadFileDialog(PersistFileDialog): def __init__(self, master, title, filter): PersistFileDialog.__init__(self, master, title, filter) self.top.title(title) def OkPressed(self): fileName = self.GetFileName() if os.path.exists(fileName) == 0: msg = 'File ' + fileName + ' not found.' errorDlg = alert.ErrorDialog(self.top, msg) errorDlg.Show() errorDlg.DialogCleanup() return PersistFileDialog.OkPressed(self) ############################################################################# # # Class: PersistSaveFileDialog # Purpose: Essentially the same as SaveFileDialog, except this class invokes # PersistFileDialog instead of FileDialog. # ############################################################################# class PersistSaveFileDialog(PersistFileDialog): def __init__(self, master, title, filter, initWProtState=None): PersistFileDialog.__init__(self, master, title, filter, initWProtState) self.top.title(title) def OkPressed(self): fileName = self.GetFileName() if os.path.exists(fileName) == 1: msg = 'File ' + fileName + ' exists.\nDo you wish to overwrite it?' warningDlg = alert.WarningDialog(self.top, msg) if warningDlg.Show() == 0: warningDlg.DialogCleanup() return warningDlg.DialogCleanup() PersistFileDialog.OkPressed(self) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647588344.0 pyraf-2.2.1/pyraf/tools/irafglobals.py0000644000175000017500000003334614215031770016514 0ustar00olesoles"""module irafglobals.py -- widely used IRAF constants and objects NOTE! This module does NOT require the installation of IRAF. It's location in stsci.tools is safe because it is intended to remain free of such dependency. yes, no Boolean values IrafError Standard IRAF exception Verbose Flag indicating verbosity level userIrafHome User's IRAF home directory (./ or ~/iraf/) userWorkingHome User's working home directory (the directory when this module gets imported.) EOF End-of-file indicator object INDEF Undefined object IrafTask "Tag" class for IrafTask type. IrafPkg "Tag" class for IrafPkg type This is defined so it is safe to say 'from irafglobals import *' The tag classes do nothing except allow checks of types via (e.g.) isinstance(o,IrafTask). Including it here decouples the other classes from the module that actually implements IrafTask, greatly reducing the need for mutual imports of modules by one another. $Id$ Taken from pyraf.irafglobals, originally signed "R. White, 2000 Jan 5" """ import os from . import compmixin _os = os _compmixin = compmixin del os, compmixin number_types = (int, float) class IrafError(Exception): def __init__(self, msg, errno=-1, errmsg="", errtask=""): Exception.__init__(self, msg) self.errno = errno self.errmsg = errmsg or msg self.errtask = errtask # ----------------------------------------------------- # Verbose: verbosity flag # ----------------------------------------------------- # make Verbose an instance of a class so it can be imported # into other modules and changed by them class _VerboseClass(_compmixin.ComparableIntBaseMixin): """Container class for verbosity (or other) value""" def __init__(self, value=0): self.value = value def set(self, value): self.value = value def get(self): return self.value def _cmpkey(self): return self.value def __nonzero__(self): return self.value != 0 def __bool__(self): return self.value != 0 def __str__(self): return str(self.value) Verbose = _VerboseClass() # ----------------------------------------------------- # userWorkingHome is current working directory # ----------------------------------------------------- userWorkingHome = _os.getcwd() # ----------------------------------------------------- # userIrafHome is location of user's IRAF home directory # ----------------------------------------------------- # If login.cl exists here, use this directory as home. # Otherwise look for ~/iraf. if _os.path.exists('./login.cl'): userIrafHome = _os.path.join(userWorkingHome,'') elif _os.path.exists(_os.path.expanduser('~/.iraf/login.cl')): userIrafHome = _os.path.expanduser('~/.iraf') else: userIrafHome = _os.path.join(_os.getenv('HOME','.'),'iraf','') if not _os.path.exists(userIrafHome): # no ~/iraf, just use '.' as home userIrafHome = _os.path.join(userWorkingHome,'') # ----------------------------------------------------- # Boolean constant class # ----------------------------------------------------- class _Boolean(_compmixin.ComparableMixin): """Class of boolean constant object""" def __init__(self, value=None): # change value to 1 or 0 if value: self.__value = 1 else: self.__value = 0 self.__strvalue = ["no", "yes"][self.__value] def __copy__(self): """Don't bother to make a copy""" return self def __deepcopy__(self, memo=None): """Don't bother to make a copy""" return self def _compare(self, other, method): # _Boolean vs. _Boolean if isinstance(other, _Boolean): return method(self.__value, other.__value) # _Boolean vs. string: # If a string, compare with string value of this parameter. # Allow uppercase "YES", "NO" as well as lowercase. # Also allows single letter abbrevation "y" or "n". if isinstance(other, str): ovalue = other.lower() if len(ovalue)==1: return method(self.__strvalue[0], ovalue) else: return method(self.__strvalue, ovalue) # _Boolean vs. all other types (int, float, bool, etc) - treat this # value like an integer return method(self.__value, other) def __nonzero__(self): return self.__value != 0 def __bool__(self): return self.__value != 0 def __repr__(self): return self.__strvalue def __str__(self): return self.__strvalue def __int__(self): return self.__value def __float__(self): return float(self.__value) # create yes, no boolean values yes = _Boolean(1) no = _Boolean(0) # ----------------------------------------------------- # define end-of-file object # if printed, says 'EOF' # if converted to integer, has value -2 (special IRAF value) # Implemented as a singleton, although the singleton # nature is not really essential # ----------------------------------------------------- class _EOFClass(_compmixin.ComparableMixin): """Class of singleton EOF (end-of-file) object""" def __init__(self): global EOF if EOF is not None: # only allow one to be created raise RuntimeError("Use EOF object, not _EOFClass") def __copy__(self): """Not allowed to make a copy""" return self def __deepcopy__(self, memo=None): """Not allowed to make a copy""" return self def _compare(self, other, method): if isinstance(other, _EOFClass): # Despite trying to create only one EOF object, there # could be more than one. All EOFs are equal. return method(1, 1) if isinstance(other, str): # If a string, compare with 'EOF' return method("EOF", other) if isinstance(other, number_types): # If a number, compare with -2 return method(-2, other) # what else could it be? return NotImplemented def __repr__(self): return "EOF" def __str__(self): return "EOF" def __int__(self): return -2 def __float__(self): return -2.0 # initialize EOF to None first so singleton scheme works EOF = None EOF = _EOFClass() # ----------------------------------------------------- # define IRAF-like INDEF object # ----------------------------------------------------- class _INDEFClass: """Class of singleton INDEF (undefined) object""" def __new__(cls): # Guido's example Singleton pattern it = cls.__dict__.get("__it__") if it is not None: return it # this use of super gets the correct version of __new__ for the # int and float subclasses too cls.__it__ = it = super(_INDEFClass, cls).__new__(cls) return it def __copy__(self): """Not allowed to make a copy""" return self def __deepcopy__(self, memo=None): """Not allowed to make a copy""" return self def __lt__(self, other): return INDEF def __le__(self, other): return INDEF def __gt__(self, other): return INDEF def __ge__(self, other): return INDEF def __eq__(self, other): # Despite trying to create only one INDEF object, there # could be more than one. All INDEFs are equal. # Also allow "INDEF" - CDS 17Nov2011 return isinstance(other, _INDEFClass) or (other and str(other)=="INDEF") def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "INDEF" def __str__(self): return "INDEF" __oct__ = __str__ __hex__ = __str__ # type conversions return various types of INDEF objects # this is necessary for Python 2.4 def __int__(self): return _INDEF_int def __long__(self): return _INDEF_int def __float__(self): return _INDEF_float def __nonzero__(self): return False # need bool return type # all operations on INDEF return INDEF def __add__(self, other): return INDEF __sub__ = __add__ __mul__ = __add__ __rmul__ = __add__ __div__ = __add__ __mod__ = __add__ __divmod__ = __add__ __pow__ = __add__ __lshift__ = __add__ __rshift__ = __add__ __and__ = __add__ __xor__ = __add__ __or__ = __add__ __radd__ = __add__ __rsub__ = __add__ __rmul__ = __add__ __rrmul__ = __add__ __rdiv__ = __add__ __rmod__ = __add__ __rdivmod__ = __add__ __rpow__ = __add__ __rlshift__ = __add__ __rrshift__ = __add__ __rand__ = __add__ __rxor__ = __add__ __ror__ = __add__ def __neg__(self): return INDEF __pos__ = __neg__ __abs__ = __neg__ __invert__ = __neg__ INDEF = _INDEFClass() # Classes that inherit from built-in types are required for Python 2.4 # so that int and float conversion functions work correctly. # Unfortunately, if you call int(_INDEF_int) it ignores the # __int__ method and returns zero, so these objects should be # used sparingly and replaced with standard INDEF whereever # possible. class _INDEFClass_int(_INDEFClass, int): pass class _INDEFClass_float(_INDEFClass, float): pass _INDEF_int = _INDEFClass_int() _INDEF_float = _INDEFClass_float() # ----------------------------------------------------- # define IRAF-like EPSILON object # ----------------------------------------------------- class _EPSILONClass(_compmixin.ComparableFloatBaseMixin): """Class of singleton EPSILON object, for floating-point comparison""" def __new__(cls): # Guido's example Singleton pattern it = cls.__dict__.get("__it__") if it is not None: return it cls.__it__ = it = super(_EPSILONClass, cls).__new__(cls) return it def __init__(self): self.__dict__["_value"] = None def setvalue(self): DEFAULT_VALUE = 1.192e-7 hlib = _os.environ.get("hlib") if hlib is None: self._value = DEFAULT_VALUE return fd = open(_os.path.join(hlib, "mach.h")) lines = fd.readlines() fd.close() foundit = 0 for line in lines: words = line.split() if len(words) < 1 or words[0] == "#": continue if words[0] == "define" and words[1] == "EPSILONR": strvalue = words[2] if strvalue[0] == "(": strvalue = strvalue[1:-1] self._value = float(strvalue) foundit = 1 break if not foundit: self._value = DEFAULT_VALUE def __copy__(self): """Not allowed to make a copy""" return self def __deepcopy__(self, memo=None): """Not allowed to make a copy""" return self def __setattr__(self, name, value): """Not allowed to modify the value or add a new attribute""" if name == "_value": if self.__dict__["_value"] is None: self.__dict__["_value"] = value else: raise RuntimeError("epsilon cannot be modified") else: pass def __delattr__(self, value): """Not allowed to delete the value""" pass def _cmpkey(self): return self._value def __repr__(self): return "%.6g" % self._value def __str__(self): return "%.6g" % self._value __oct__ = None __hex__ = None def __int__(self): return 0 def __long__(self): return 0 def __float__(self): return self._value def __nonzero__(self): return True # need bool return type def __add__(self, other): return self._value + other def __sub__(self, other): return self._value - other def __mul__(self, other): return self._value * other def __div__(self, other): return self._value / other def __mod__(self, other): return self._value % other def __divmod__(self, other): return (self._value // other, self._value % other) def __pow__(self, other): return self._value ** other def __neg__(self): return -self._value def __pos__(self): return self._value def __abs__(self): return abs(self._value) # arguments in reverse order def __radd__(self, other): return other + self._value def __rsub__(self, other): return other - self._value def __rmul__(self, other): return other * self._value def __rdiv__(self, other): return other / self._value def __rmod__(self, other): return other % self._value def __rdivmod__(self, other): return (other // self._value, other % self._value) def __rpow__(self, other): return other ** self._value epsilon = _EPSILONClass() epsilon.setvalue() # ----------------------------------------------------- # Float class that reproduces Py2 str precision on Py3 # ----------------------------------------------------- class clFloat(float): def __str__(self): return '{:.12}'.format(self) for op in ('__add__', '__radd__', '__sub__', '__rsub__', '__mul__', '__rmul__', '__div__', '__rdiv__', '__truediv__', '__rtruediv__', '__floordiv__', '__rfloordiv__', '__pow__', '__rpow__', '__mod__', '__abs__', '__neg__', '__pos__'): exec(f'def {op}(self, *args):\n' f' val = super(clFloat, self).{op}(*args)\n' ' return val if val is NotImplemented else clFloat(val)') def __divmod__(self, other): return tuple(clFloat(v) for v in super().__divmod__(other)) def __rdivmod__(self, other): return tuple(clFloat(v) for v in super().__rdivmod__(other)) # ----------------------------------------------------- # tag classes # ----------------------------------------------------- class IrafTask: pass class IrafPkg(IrafTask): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/irafutils.py0000644000175000017500000004554514214070451016233 0ustar00olesoles"""module irafutils.py -- general utility functions printCols Print elements of list in cols columns printColsAuto Print elements of list in the best number of columns stripQuotes Strip single or double quotes off string and remove embedded quote pairs csvSplit Split comma-separated fields in strings (cover bug in csv mod) rglob Recursive glob setWritePrivs Convenience function to add/remove write privs removeEscapes Remove escaped quotes & newlines from strings translateName Convert CL parameter or variable name to Python-acceptable name untranslateName Undo Python conversion of CL parameter or variable name tkread Read n bytes from file while running Tk mainloop tkreadline Read a line from file while running Tk mainloop launchBrowser Given a URL, try to pop it up in a browser on most platforms. $Id$ R. White, 1999 Jul 16 """ import os import stat import string import sys import re import fnmatch import keyword import select from . import capable if capable.OF_GRAPHICS: import tkinter as TKNTR def printColsAuto(in_strings, term_width=80, min_pad=1): """ Print a list of strings centered in columns. Determine the number of columns and lines on the fly. Return the result, ready to print. in_strings is a list/tuple/iterable of strings min_pad is number of spaces to appear on each side of a single string (so you will see twice this many spaces between 2 strings) """ # sanity check if not in_strings: raise ValueError('Unexpected: ' + repr(in_strings)) # get max width in input maxWidth = len(max(in_strings, key=len)) + (2*min_pad) # width with pad numCols = term_width//maxWidth # integer div # set numCols so we take advantage of the whole line width numCols = min(numCols, len(in_strings)) # easy case - single column or too big if numCols < 2: # one or some items are too big but print one item per line anyway lines = [x.center(term_width) for x in in_strings] return '\n'.join(lines) # normal case - 2 or more columns colWidth = term_width//numCols # integer div # colWidth is guaranteed to be larger than all items in input retval = '' for i in range(len(in_strings)): retval+=in_strings[i].center(colWidth) if (i+1)%numCols == 0: retval += '\n' return retval.rstrip() def printCols(strlist,cols=5,width=80): """Print elements of list in cols columns""" # This may exist somewhere in the Python standard libraries? # Should probably rewrite this, it is pretty crude. nlines = (len(strlist)+cols-1)//cols line = nlines*[""] for i in range(len(strlist)): c, r = divmod(i,nlines) nwid = c*width//cols - len(line[r]) if nwid>0: line[r] = line[r] + nwid*" " + strlist[i] else: line[r] = line[r] + " " + strlist[i] for s in line: print(s) _re_doubleq2 = re.compile('""') _re_singleq2 = re.compile("''") def stripQuotes(value): """Strip single or double quotes off string; remove embedded quote pairs""" if value[:1] == '"': value = value[1:] if value[-1:] == '"': value = value[:-1] # replace "" with " value = re.sub(_re_doubleq2, '"', value) elif value[:1] == "'": value = value[1:] if value[-1:] == "'": value = value[:-1] # replace '' with ' value = re.sub(_re_singleq2, "'", value) return value def csvSplit(line, delim=',', allowEol=True): """ Take a string as input (e.g. a line in a csv text file), and break it into tokens separated by commas while ignoring commas embedded inside quoted sections. This is exactly what the 'csv' module is meant for, so we *should* be using it, save that it has two bugs (described next) which limit our use of it. When these bugs are fixed, this function should be forsaken in favor of direct use of the csv module (or similar). The basic use case is to split a function signature string, so for: afunc(arg1='str1', arg2='str, with, embedded, commas', arg3=7) we want a 3 element sequence: ["arg1='str1'", "arg2='str, with, embedded, commas'", "arg3=7"] but: >>> import csv >>> y = "arg1='str1', arg2='str, with, embedded, commas', arg3=7" >>> rdr = csv.reader( (y,), dialect='excel', quotechar="'", skipinitialspace=True) >>> l = rdr.next(); print(len(l), str(l)) # doctest: +SKIP 6 ["arg1='str1'", "arg2='str", 'with', 'embedded', "commas'", "arg3=7"] which we can see is not correct - we wanted 3 tokens. This occurs in Python 2.5.2 and 2.6. It seems to be due to the text at the start of each token ("arg1=") i.e. because the quote isn't for the whole token. If we were to remove the names of the args and the equal signs, it works: >>> x = "'str1', 'str, with, embedded, commas', 7" >>> rdr = csv.reader( (x,), dialect='excel', quotechar="'", skipinitialspace=True) >>> l = rdr.next(); print(len(l), str(l)) # doctest: +SKIP 3 ['str1', 'str, with, embedded, commas', '7'] But even this usage is delicate - when we turn off skipinitialspace, it fails: >>> x = "'str1', 'str, with, embedded, commas', 7" >>> rdr = csv.reader( (x,), dialect='excel', quotechar="'") >>> l = rdr.next(); print(len(l), str(l)) # doctest: +SKIP 6 ['str1', " 'str", ' with', ' embedded', " commas'", ' 7'] So, for now, we'll roll our own. """ # Algorithm: read chars left to right, go from delimiter to delimiter, # but as soon as a single/double/triple quote is hit, scan forward # (ignoring all else) until its matching end-quote is found. # For now, we will not specially handle escaped quotes. tokens = [] ldl = len(delim) keepOnRollin = line is not None and len(line) > 0 while keepOnRollin: tok = _getCharsUntil(line, delim, True, allowEol=allowEol) # len of token should always be > 0 because it includes end delimiter # except on last token if len(tok) > 0: # append it, but without the delimiter if tok[-ldl:] == delim: tokens.append(tok[:-ldl]) else: tokens.append(tok) # tok goes to EOL - has no delimiter keepOnRollin = False line = line[len(tok):] else: # This is the case of the empty end token tokens.append('') keepOnRollin = False return tokens # We'll often need to search a string for 3 possible characters. We could # loop and check each one ourselves; we could do 3 separate find() calls; # or we could do a compiled re.search(). For VERY long strings (hundreds # of thousands of chars), it turns out that find() is so fast and that # re (even compiled) has enough overhead, that 3 find's is the same or # slightly faster than one re.search with three chars in the re expr. # Of course, both methods are much faster than an explicit loop. # Since these strings will be short, the fastest method is re.search() _re_sq = re.compile(r"'") _re_dq = re.compile(r'"') _re_comma_sq_dq = re.compile(r'[,\'"]') def _getCharsUntil(buf, stopChar, branchForQuotes, allowEol): # Sanity checks if buf is None: return None if len(buf) <= 0: return '' # Search chars left-to-right looking for stopChar sought = (stopChar,) theRe = None if branchForQuotes: sought = (stopChar,"'",'"') # see later, we'll handle '"""' too if stopChar == ',': theRe = _re_comma_sq_dq # pre-compiled common case else: if stopChar == '"': theRe = _re_dq # pre-compiled common case if stopChar == "'": theRe = _re_sq # pre-compiled common case if theRe is None: theRe = re.compile('['+''.join(sought)+']') mo = theRe.search(buf) # No match found; stop if mo is None: if not stopChar in ('"', "'"): # this is a primary search, not a branch into quoted text return buf # searched until we hit the EOL, must be last token else: # this is a branch into a quoted string - do we allow EOL here? if allowEol: return buf else: raise ValueError('Unfound end-quote, buffer: '+buf) # The expected match was found. Stop. if mo.group() == stopChar: return buf[:1 + mo.start()] # return token plus stopChar at end # Should not get to this point unless in a branch-for-quotes situation. if not branchForQuotes: raise RuntimeError("Programming error! Shouldn't be here w/out branching") # Quotes were found. # There are two kinds, but double quotes could be the start of # triple double-quotes. (""") So get the substring to create the token. # # token = preQuote+quotedPart+postQuote (e.g.: "abc'-hi,ya-'xyz") # preQuote = buf[:mo.start()] if mo.group() == "'": quotedPart = "'"+_getCharsUntil(buf[1+mo.start():],"'",False,allowEol) else: # first double quote (are there 3 in a row?) idx = mo.start() if len(buf) > idx+2 and '"""' == buf[idx:idx+3]: # We ARE in a triple-quote sub-string end_t_q = buf[idx+3:].find('"""') if end_t_q < 0: # hit end of line before finding end quote if allowEol: quotedPart = buf[idx:] else: raise ValueError('Unfound triple end-quote, buffer: '+buf) else: quotedPart = buf[idx:idx+3+end_t_q+1] else: quotedPart = '"'+_getCharsUntil(buf[1+mo.start():],'"',False,allowEol) lenSoFar = len(preQuote)+len(quotedPart) if lenSoFar < len(buf): # now get back to looking for end delimiter postQuote = _getCharsUntil(buf[lenSoFar:], stopChar, branchForQuotes, allowEol) return preQuote+quotedPart+postQuote else: return buf # at end def rglob(root, pattern): """ Same thing as glob.glob, but recursively checks subdirs. """ # Thanks to Alex Martelli for basics on Stack Overflow retlist = [] if None not in (pattern, root): for base, dirs, files in os.walk(root): goodfiles = fnmatch.filter(files, pattern) retlist.extend(os.path.join(base, f) for f in goodfiles) return retlist def setWritePrivs(fname, makeWritable, ignoreErrors=False): """ Set a file named fname to be writable (or not) by user, with the option to ignore errors. There is nothing ground-breaking here, but I was annoyed with having to repeate this little bit of code. """ privs = os.stat(fname).st_mode try: if makeWritable: os.chmod(fname, privs | stat.S_IWUSR) else: os.chmod(fname, privs & (~ stat.S_IWUSR)) except OSError: if ignoreErrors: pass # just try, don't whine else: raise def removeEscapes(value, quoted=0): r"""Remove escapes from in front of quotes (which IRAF seems to just stick in for fun sometimes.) Remove \-newline too. If quoted is true, removes all blanks following \-newline (which is a nasty thing IRAF does for continuations inside quoted strings.) XXX Should we remove \\ too? """ i = value.find(r'\"') while i>=0: value = value[:i] + value[i+1:] i = value.find(r'\"',i+1) i = value.find(r"\'") while i>=0: value = value[:i] + value[i+1:] i = value.find(r"\'",i+1) # delete backslash-newlines i = value.find("\\\n") while i>=0: j = i+2 if quoted: # ignore blanks and tabs following \-newline in quoted strings for c in value[i+2:]: if c not in ' \t': break j = j+1 value = value[:i] + value[j:] i = value.find("\\\n",i+1) return value # Must modify Python keywords to make Python code legal. I add 'PY' to # beginning of Python keywords (and some other illegal Python identifiers). # It will be stripped off where appropriate. def translateName(s, dot=0): """Convert CL parameter or variable name to Python-acceptable name Translate embedded dollar signs to 'DOLLAR' Add 'PY' prefix to components that are Python reserved words Add 'PY' prefix to components start with a number If dot != 0, also replaces '.' with 'DOT' """ s = s.replace('$', 'DOLLAR') sparts = s.split('.') for i in range(len(sparts)): if sparts[i] == "" or sparts[i][0] in string.digits or \ keyword.iskeyword(sparts[i]): sparts[i] = 'PY' + sparts[i] if dot: return 'DOT'.join(sparts) else: return '.'.join(sparts) def untranslateName(s): """Undo Python conversion of CL parameter or variable name""" s = s.replace('DOT', '.') s = s.replace('DOLLAR', '$') # delete 'PY' at start of name components if s[:2] == 'PY': s = s[2:] s = s.replace('.PY', '.') return s # procedures to read while still allowing Tk widget updates def init_tk_default_root(withdraw=True): """ In case the _default_root value is required, you may safely call this ahead of time to ensure that it has been initialized. If it has already been, this is a no-op. """ if not capable.OF_GRAPHICS: raise RuntimeError("Cannot run this command without graphics") if not TKNTR._default_root: # TKNTR imported above junk = TKNTR.Tk() # tkinter._default_root is now populated (== junk) retval = TKNTR._default_root if withdraw and retval: retval.withdraw() return retval def tkread(file, n=0): """Read n bytes from file (or socket) while running Tk mainloop. If n=0 then this runs the mainloop until some input is ready on the file. (See tkreadline for an application of this.) The file must have a fileno method. """ return _TkRead().read(file, n) def tkreadline(file=None): """Read a line from file while running Tk mainloop. If the file is not line-buffered then the Tk mainloop will stop running after one character is typed. The function will still work but Tk widgets will stop updating. This should work OK for stdin and other line-buffered filehandles. If file is omitted, reads from sys.stdin. The file must have a readline method. If it does not have a fileno method (which can happen e.g. for the status line input on the graphics window) then the readline method is simply called directly. """ if file is None: file = sys.stdin if not hasattr(file, "readline"): raise TypeError("file must be a filehandle with a readline method") # Call tkread now... # BUT, if we get in here for something not GUI-related (e.g. terminal- # focused code in a sometimes-GUI app) then skip tkread and simply call # readline on the input eg. stdin. Otherwise we'd fail in _TkRead().read() try: fd = file.fileno() except: fd = None if (fd and capable.OF_GRAPHICS): tkread(fd, 0) # if EOF was encountered on a tty, avoid reading again because # it actually requests more data if not select.select([fd],[],[],0)[0]: return '' return file.readline() class _TkRead: """Run Tk mainloop while waiting for a pending read operation""" def read(self, file, nbytes): """Read nbytes characters from file while running Tk mainloop""" if not capable.OF_GRAPHICS: raise RuntimeError("Cannot run this command without graphics") if isinstance(file, int): fd = file else: # Otherwise, assume we have Python file object try: fd = file.fileno() except: raise TypeError("file must be an integer or a filehandle/socket") init_tk_default_root() # harmless if already done self.widget = TKNTR._default_root if not self.widget: # no Tk widgets yet, so no need for mainloop # (shouldnt happen now with init_tk_default_root) s = [] while nbytes>0: snew = os.read(fd, nbytes) if snew: snew = snew.decode('ascii', 'replace') s.append(snew) nbytes -= len(snew) else: # EOF -- just return what we have so far break return "".join(s) else: self.nbytes = nbytes self.value = [] self.widget.tk.createfilehandler(fd, TKNTR.READABLE | TKNTR.EXCEPTION, self._read) try: self.widget.mainloop() finally: self.widget.tk.deletefilehandler(fd) return "".join(self.value) def _read(self, fd, mask): """Read waiting data and terminate Tk mainloop if done""" try: # if EOF was encountered on a tty, avoid reading again because # it actually requests more data if select.select([fd],[],[],0)[0]: snew = os.read(fd, self.nbytes) snew = snew.decode('ascii', 'replace') self.value.append(snew) self.nbytes -= len(snew) else: snew = '' if (self.nbytes <= 0 or len(snew) == 0) and self.widget: # stop the mainloop self.widget.quit() except OSError: raise IOError("Error reading from %s" % (fd,)) def launchBrowser(url, brow_bin='mozilla', subj=None): """ Given a URL, try to pop it up in a browser on most platforms. brow_bin is only used on OS's where there is no "open" or "start" cmd. """ if not subj: subj = url # Tries to use webbrowser module on most OSes, unless a system command # is needed. (E.g. win, linux, sun, etc) if sys.platform not in ('os2warp, iphone'): # try webbrowser w/ everything? import webbrowser if not webbrowser.open(url): print("Error opening URL: "+url) else: print('Help on "'+subj+'" is now being displayed in a web browser') return # Go ahead and fork a subprocess to call the correct binary pid = os.fork() if pid == 0: # child if sys.platform == 'darwin': if os.system('open "'+url+'"'): # does not seem to keep '#.*' # nosec print("Error opening URL: "+url) os._exit(0) # The following retries if "-remote" doesnt work, opening a new browser # cmd = brow_bin+" -remote 'openURL("+url+")' '"+url+"' 1> /dev/null 2>&1" # if 0 != os.system(cmd) # print "Running "+brow_bin+" for HTML help..." # os.execvp(brow_bin,[brow_bin,url]) # os._exit(0) else: # parent if not subj: subj = url print('Help on "'+subj+'" is now being displayed in a browser') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/listdlg.py0000644000175000017500000000664614214070451015672 0ustar00olesoles# # A home-grown list-selection convenience dialog. As *soon* as tkinter comes # with one of these, replace all uses of this one with that. This currently # only allows single selection. # """ $Id$ """ from . import capable, irafutils if capable.OF_GRAPHICS: from tkinter import * # noqa from tkinter.simpledialog import Dialog else: Dialog = object class ListSingleSelectDialog(Dialog): def __init__(self, title, prompt, choiceList, parent=None): if not parent: parent = irafutils.init_tk_default_root() self.__prompt = prompt self.__choices = choiceList self.__retval = None self.__clickedOK = False parent.update() Dialog.__init__(self, parent, title) # enters main loop here def get_current_index(self): """ Return currently selected index (or -1) """ # Need to convert to int; currently API returns a tuple of string curSel = self.__lb.curselection() if curSel and len(curSel) > 0: return int(curSel[0]) else: return -1 def getresult(self): return self.__retval def destroy(self): # first save the selected index before it is destroyed idx = self.get_current_index() # in PyRAF, assume they meant the first one if they clicked nothing, # since it is already active (underlined) if idx < 0: idx = 0 # get the object at that index if self.__clickedOK and idx >= 0: # otherwise is None self.__retval = self.__choices[idx] if self.__retval and type(self.__retval) == str: self.__retval = self.__retval.strip() # now destroy self.__lb = None Dialog.destroy(self) def body(self, master): label = Label(master, text=self.__prompt, justify=LEFT) # label.grid(row=0, padx=8, sticky=W) label.pack(side=TOP, fill=X, padx=10, pady=8) frame = Frame(master) # frame.grid(row=1, padx=8, sticky=W+E) frame.pack(side=TOP, fill=X, padx=10, pady=8) vscrollbar = Scrollbar(frame, orient=VERTICAL) hscrollbar = Scrollbar(frame, orient=HORIZONTAL) self.__lb = Listbox(frame, selectmode=BROWSE, xscrollcommand=hscrollbar.set, yscrollcommand=vscrollbar.set) # activestyle='none', # none = dont underline items hscrollbar.config(command=self.__lb.xview) hscrollbar.pack(side=BOTTOM, fill=X) vscrollbar.config(command=self.__lb.yview) vscrollbar.pack(side=RIGHT, fill=Y) self.__lb.pack(side=LEFT, fill=BOTH, expand=1) for itm in self.__choices: self.__lb.insert(END, str(itm)) self.__lb.bind("", self.ok) # dbl clk # self.__lb.selection_set(0,0) self.__lb.focus_set() return self.__lb def ok(self, val=None): self.__clickedOK = True # save that this wasn't a cancel Dialog.ok(self, val) def validate(self): return 1 if __name__ == "__main__": """This is for manual testing only because it is interactive.""" root = Tk() root.withdraw() root.update() x = ListSingleSelectDialog("Select Parameter File", \ "Select which file you prefer for task/pkg:", \ ['abc','def','ghi','jkl','1'], None) print(str(x.getresult())) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/minmatch.py0000644000175000017500000002042114214070451016013 0ustar00olesoles"""minmatch.py: Dictionary allowing minimum-match of string keys Entries can be retrieved using an abbreviated key as long as the key is unambiguous. __getitem__ and get() raise an error if the key is ambiguous. A key is not consider ambiguous if it matches a full key, even if it also is an abbreviation for a longer key. E.g., if there are keys 'spam' and 'spameggs' in the dictionary, d['spam'] returns the value associated with 'spam', while d['spa'] is an error due to ambiguity. New key/value pairs must be inserted using the add() method to avoid ambiguities with when to overwrite and when to add a new key. Assignments using setitem (e.g. d[key] = value) will raise an exception unless the key already exists and is unambiguous. The getall(key) method returns a list of all the matching values, containing a single entry for unambiguous matches and multiple entries for ambiguous matches. $Id$ R. White, 2000 January 28 """ import copy from collections import UserDict class AmbiguousKeyError(KeyError): pass class MinMatchDict(UserDict): def __init__(self,indict=None,minkeylength=1): self.data = {} # use lazy instantiation for min-match dictionary # it may never be created if full keys are always used self.mmkeys = None if minkeylength<1: minkeylength = 1 self.minkeylength = minkeylength if indict is not None: add = self.add for key in indict.keys(): add(key, indict[key]) def __deepcopy__(self, memo=None): """Deep copy of dictionary""" # this is about twice as fast as the default implementation return self.__class__(copy.deepcopy(self.data,memo), self.minkeylength) def __getinitargs__(self): """Return __init__ args for pickle""" return (self.data, self.minkeylength) def _mmInit(self): """Create the minimum match dictionary of keys""" # cache references to speed up loop a bit mmkeys = {} mmkeysGet = mmkeys.setdefault minkeylength = self.minkeylength for key in self.data.keys(): # add abbreviations as short as minkeylength # always add at least one entry (even for key="") lenkey = len(key) start = min(minkeylength,lenkey) for i in range(start,lenkey+1): mmkeysGet(key[0:i],[]).append(key) self.mmkeys = mmkeys def getfullkey(self, key, new=0): # check for exact match first # ambiguous key is ok if there is exact match if key in self.data: return key if not isinstance(key, str): raise KeyError("MinMatchDict keys must be strings") # no exact match, so look for unique minimum match if self.mmkeys is None: self._mmInit() keylist = self.mmkeys.get(key) if keylist is None: # no such key -- ok only if new flag is set if new: return key raise KeyError("Key "+key+" not found") elif len(keylist) == 1: # unambiguous key return keylist[0] else: return self.resolve(key,keylist) def resolve(self, key, keylist): """Hook to resolve ambiguities in selected keys""" raise AmbiguousKeyError("Ambiguous key "+ repr(key) + ", could be any of " + str(sorted(keylist))) def add(self, key, item): """Add a new key/item pair to the dictionary. Resets an existing key value only if this is an exact match to a known key.""" mmkeys = self.mmkeys if mmkeys is not None and not (key in self.data): # add abbreviations as short as minkeylength # always add at least one entry (even for key="") lenkey = len(key) start = min(self.minkeylength,lenkey) # cache references to speed up loop a bit mmkeysGet = mmkeys.setdefault for i in range(start,lenkey+1): mmkeysGet(key[0:i],[]).append(key) self.data[key] = item def __setitem__(self, key, item): """Set value of existing key/item in dictionary""" try: key = self.getfullkey(key) self.data[key] = item except KeyError as e: raise e.__class__(str(e) + "\nUse add() method to add new items") def __getitem__(self, key): try: # try the common case that the exact key is given first return self.data[key] except KeyError: return self.data[self.getfullkey(key)] def get(self, key, failobj=None, exact=0): """Raises exception if key is ambiguous""" if not exact: key = self.getfullkey(key,new=1) return self.data.get(key,failobj) def get_exact_key(self, key, failobj=None): """Returns failobj if key does not match exactly""" return self.data.get(key,failobj) def __delitem__(self, key): key = self.getfullkey(key) del self.data[key] if self.mmkeys is not None: start = min(self.minkeylength,len(key)) for i in range(start,len(key)+1): s = key[0:i] value = self.mmkeys.get(s) value.remove(key) if not value: # delete entry from mmkeys if that was last value del self.mmkeys[s] def clear(self): self.mmkeys = None self.data.clear() def __contains__(self, key): """For the "in" operator. Raise an exception if key is ambiguous""" return self._has(key) def has_key(self, key, exact=0): return self._has(key, exact) def _has(self, key, exact=0): """Raises an exception if key is ambiguous""" if not exact: key = self.getfullkey(key,new=1) return key in self.data def has_exact_key(self, key): """Returns true if there is an exact match for this key""" return key in self.data def update(self, other): # check for missing attrs (needed in python 2.7) if not hasattr(self, 'data'): self.data = {} if not hasattr(self, 'mmkeys'): self.mmkeys = None if not hasattr(self, 'minkeylength'): self.minkeylength = other.minkeylength # now do the update from 'other' if type(other) is type(self.data): for key in other.keys(): self.add(key,other[key]) else: for key, value in other.items(): self.add(key,value) def getall(self, key, failobj=None): """Returns a list of all the matching values for key, containing a single entry for unambiguous matches and multiple entries for ambiguous matches.""" if self.mmkeys is None: self._mmInit() k = self.mmkeys.get(key) if not k: return failobj return list(map(self.data.get, k)) def getallkeys(self, key, failobj=None): """Returns a list of the full key names (not the items) for all the matching values for key. The list will contain a single entry for unambiguous matches and multiple entries for ambiguous matches.""" if self.mmkeys is None: self._mmInit() return self.mmkeys.get(key, failobj) class QuietMinMatchDict(MinMatchDict): """Minimum match dictionary that does not raise unexpected AmbiguousKeyError Unlike MinMatchDict, if key is ambiguous then both get() and has_key() methods return false (just as if there is no match). For most uses this is probably not the preferred behavior (use MinMatchDict instead), but for applications that rely on the usual dictionary behavior where .get() and .has_key() do not raise exceptions, this is useful. """ def get(self, key, failobj=None, exact=0): """Returns failobj if key is not found or is ambiguous""" if not exact: try: key = self.getfullkey(key) except KeyError: return failobj return self.data.get(key,failobj) def _has(self, key, exact=0): """Returns false if key is not found or is ambiguous""" if not exact: try: key = self.getfullkey(key) return 1 except KeyError: return 0 else: return key in self.data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/taskpars.py0000644000175000017500000000512314214070451016045 0ustar00olesoles""" Contains the TaskPars class and any related functions. $Id$ """ class NoExecError(Exception): pass class TaskPars: """ This represents a task's collection of configurable parameters. This class is meant to be mostly abstract, though there is some functionality included which could be common to most derived classes. This also serves to document the interface which must be met for EPAR. """ def getName(self, *args, **kw): """ Returns the string name of the task. """ raise NotImplementedError("class TaskPars is not to be used directly") def getPkgname(self, *args, **kw): """ Returns the string name of the package, if applicable. """ raise NotImplementedError("class TaskPars is not to be used directly") def getParList(self, *args, **kw): """ Returns a list of parameter objects. """ raise NotImplementedError("class TaskPars is not to be used directly") def getDefaultParList(self, *args, **kw): """ Returns a list of parameter objects with default values set. """ raise NotImplementedError("class TaskPars is not to be used directly") def setParam(self, *args, **kw): """ Allows one to set the value of a single parameter. Initial signature is setParam(name, value, scope='', check=1) """ raise NotImplementedError("class TaskPars is not to be used directly") def getFilename(self, *args, **kw): """ Returns the string name of any associated config/parameter file. """ raise NotImplementedError("class TaskPars is not to be used directly") def saveParList(self, *args, **kw): """ Allows one to save the entire set to a file. """ raise NotImplementedError("class TaskPars is not to be used directly") def run(self, *args, **kw): """ Runs the task with the known parameters. """ raise NoExecError("Bug: class TaskPars is not to be used directly") def canPerformValidation(self): """ Returns bool. If True, expect tryValue() to be called next. """ return False def knowAsNative(self): """ Returns bool. Return true if the class prefers in-memory objects to keep (know) their parameter values in native format instead of as strings. """ return False def getHelpAsString(self): """ Meant to be overridden - return a task specific help string. """ return 'No help string available for task "'+self.getName()+'".\n '+ \ 'Implement getHelpAsString() in your TaskPars sub-class.' # also, eparam, lParam, tParam, dParam, tryValue ? ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/teal.py0000644000175000017500000014407414214070451015153 0ustar00olesoles""" Main module for the ConfigObj version of the parameter task editor: TEAL. $Id$ """ import os import sys import traceback import configobj from . import cfgpars, editpar, vtor_checks from .cfgpars import APP_NAME from .irafutils import printColsAuto, rglob, setWritePrivs from . import capable if capable.OF_GRAPHICS: from tkinter.filedialog import askopenfilename from tkinter.messagebox import showerror, showwarning # tool help tealHelpString = """\ The TEAL (Task Editor And Launcher) GUI is used to edit task parameters in a parameter-dependent way. After editing, it allows the user to launch (execute) the task. It also allows the user to view task help 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 resetting the parameter (restoring its value to the task default), clearing the value, or even activating a file browser that allows a filename to be selected and entered into 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 intuitive, 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. 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 Start the task running with the currently edited parameter values. If the Option "Save and Close on Execute" is set, this will save all the parameters and close the editor window. Save Save the parameters to the file named in the title bar. This does not close the editor window, nor does it execute the task. If however, this button appears as "Save & Quit", then it will in fact close the editor window after saving. Save As... Save the parameters to a user-specified file. This does not close the editor window, nor does it execute the task. Defaults Reset all parameters to the system default values for this task. Note that individual parameters can be reset using the menu shown by right-clicking on the parameter entry. Close Close the parameter editor. If there are unsaved changes, the user is prompted to save them. Either way, this action returns to the calling routine a Python dict of the currently selected parameter values. Cancel Cancel the editing session by exiting the parameter editor. All recent changes that were made to the parameters are lost (going back until the last Save or Save As). This action returns a Python None to the calling routine. Open... menu: Load and edit parameters from any applicable file found for the current task. This changes the current file being edited (see the name listed in the title bar) to the one selected to be opened. If no such files are found, this menu is not shown. Options menu: Display Task Help in a Window Help on the 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. Any HTML version of the task's help need to be provided by the task. Save and Close on Execute If this option is selected, the parameter editing window will be closed right before task execution as if the Close button had been clicked. This is the default behavior. For short-running tasks, it may be interesting to leave TEAL open and continue to execute while tweaking certain parameter values. Help menu: Task Help Display help on the 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. TEAL 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 (or Save & Quit), Close, Cancel, and Defaults. 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. """ # Starts a GUI session, or simply loads a file def teal(theTask, parent=None, loadOnly=False, returnAs="dict", canExecute=True, strict=False, errorsToTerm=False, autoClose=True, defaults=False): # overrides=None): """ Start the GUI session, or simply load a task's ConfigObj. """ if loadOnly: # this forces returnAs="dict" obj = None try: obj = cfgpars.getObjectFromTaskArg(theTask, strict, defaults) # obj.strictUpdate(overrides) # ! would need to re-verify after this ! except Exception as re: # catches RuntimeError and KeyError and ... # Since we are loadOnly, don't pop up the GUI for this if strict: raise else: print(re.message.replace('\n\n','\n')) return obj else: if returnAs not in ("dict", "status", None): raise ValueError("Invalid value for returnAs arg: " + str(returnAs)) dlg = None try: # if setting to all defaults, go ahead and load it here, pre-GUI if defaults: theTask = cfgpars.getObjectFromTaskArg(theTask, strict, True) # now create/run the dialog dlg = ConfigObjEparDialog(theTask, parent=parent, autoClose=autoClose, strict=strict, canExecute=canExecute) # overrides=overrides) except cfgpars.NoCfgFileError as ncf: log_last_error() if errorsToTerm: print(str(ncf).replace('\n\n','\n')) else: popUpErr(parent=parent,message=str(ncf),title="Unfound Task") except Exception as re: # catches RuntimeError and KeyError and ... log_last_error() if errorsToTerm: print(re.message.replace('\n\n','\n')) else: popUpErr(parent=parent, message=re.message, title="Bad Parameters") # Return, depending on the mode in which we are operating if returnAs is None: return if returnAs == "dict": if dlg is None or dlg.canceled(): return None else: return dlg.getTaskParsObj() # else, returnAs == "status" if dlg is None or dlg.canceled(): return -1 if dlg.executed(): return 1 return 0 # save/closed # Note that you should be careful not to use "status" and # autoClose=False, because the user can Save then Cancel def load(theTask, canExecute=True, strict=True, defaults=False): """ Shortcut to load TEAL .cfg files for non-GUI access where loadOnly=True. """ return teal(theTask, parent=None, loadOnly=True, returnAs="dict", canExecute=canExecute, strict=strict, errorsToTerm=True, defaults=defaults) def log_last_error(): import time f = open(cfgpars.getAppDir()+os.sep+'last_error.txt','w') f.write(time.asctime()+'\n\n') f.write(traceback.format_exc()+'\n') f.close() def unlearn(taskPkgName, deleteAll=False): """ Find the task named taskPkgName, and delete any/all user-owned .cfg files in the user's resource directory which apply to that task. Like a unix utility, this returns 0 on success (no files found or only 1 found but deleted). For multiple files found, this uses deleteAll, returning the file-name-list if deleteAll is False (to indicate the problem) and without deleting any files. MUST check return value. This does not prompt the user or print to the screen. """ # this WILL throw an exception if the taskPkgName isn't found flist = cfgpars.getUsrCfgFilesForPyPkg(taskPkgName) # can raise if flist is None or len(flist) == 0: return 0 if len(flist) == 1: os.remove(flist[0]) return 0 # at this point, we know more than one matching file was found if deleteAll: for f in flist: os.remove(f) return 0 else: return flist # let the caller know this is an issue def diffFromDefaults(theTask, report=False): """ Load the given file (or existing object), and return a dict of its values which are different from the default values. If report is set, print to stdout the differences. """ # get the 2 dicts (trees: dicts of dicts) defaultTree = load(theTask, canExecute=False, strict=True, defaults=True) thisTree = load(theTask, canExecute=False, strict=True, defaults=False) # they must be flattenable defaultFlat = cfgpars.flattenDictTree(defaultTree) thisFlat = cfgpars.flattenDictTree(thisTree) # use the "set" operations till there is a dict.diff() # thanks to: http://stackoverflow.com/questions/715234 diffFlat = dict( set(thisFlat.items()) - \ set(defaultFlat.items()) ) if report: defaults_of_diffs_only = {} # { k:defaultFlat[k] for k in diffFlat.keys() } for k in diffFlat: defaults_of_diffs_only[k] = defaultFlat[k] msg = 'Non-default values of "'+str(theTask)+'":\n'+ \ _flat2str(diffFlat)+ \ '\n\nDefault values:\n'+ \ _flat2str(defaults_of_diffs_only) print(msg) return diffFlat def _flat2str(fd): # waiting for a nice pretty-print rv = '{\n' for k in fd.keys(): rv += repr(k)+': '+repr(fd[k])+'\n' return rv+'}' def _isInstalled(fullFname): """ Return True if the given file name is located in an installed area (versus a user-owned file) """ if not fullFname: return False if not os.path.exists(fullFname): return False import site instAreas = site.getsitepackages() if len(instAreas) < 1: instAreas = [ os.path.dirname(os.__file__) ] for ia in instAreas: if fullFname.find(ia) >= 0: return True return False def popUpErr(parent=None, message="", title="Error"): # withdraw root, could standardize w/ EditParDialog.__init__() if parent is None: import tkinter root = tkinter.Tk() # root.lift() root.after_idle(root.withdraw) showerror(message=message, title=title, parent=parent) # We'd love to somehow force the dialog to the front here in popUpErr (on OSX) # but cannot since the Python process started from the Terminal is not an # Aqua app (unless it became so within PyRAF). This thread # http://objectmix.com/python/350288-tkinter-osx-lift.html # describes it well. def execEmbCode(SCOPE, NAME, VAL, TEAL, codeStr): """ .cfgspc embedded code execution is done here, in a relatively confined space. The variables available to the code to be executed are: SCOPE, NAME, VAL, PARENT, TEAL The code string itself is expected to set a var named OUT """ PARENT = None if TEAL: PARENT = TEAL.top OUT = None ldict = locals() # will have OUT in it exec(codeStr, globals(), ldict) # nosec return ldict['OUT'] def print_tasknames(pkgName, aDir, term_width=80, always=False, hidden=None): """ Print a message listing TEAL-enabled tasks available under a given installation directory (where pkgName resides). If always is True, this will always print when tasks are found; otherwise it will only print found tasks when in interactive mode. The parameter 'hidden' supports a list of input tasknames that should not be reported even though they still exist. """ # See if we can bail out early # sys.ps1 is only defined in interactive mode if not always and not hasattr(sys, 'ps1'): return # leave here, we're in someone's script # Check for tasks taskDict = cfgpars.findAllCfgTasksUnderDir(aDir) tasks = [x for x in taskDict.values() if len(x) > 0] if hidden: # could even account for a single taskname as input here if needed for x in hidden: if x in tasks: tasks.remove(x) # only be verbose if there something found if len(tasks) > 0: sortedUniqTasks = sorted(set(tasks)) if len(sortedUniqTasks) == 1: tlines = 'The following task in the '+pkgName+\ ' package can be run with TEAL:\n' else: tlines = 'The following tasks in the '+pkgName+\ ' package can be run with TEAL:\n' tlines += printColsAuto(sortedUniqTasks, term_width=term_width, min_pad=2) print(tlines) def getHelpFileAsString(taskname,taskpath): """ This functions will return useful help as a string read from a file in the task's installed directory called ".help". If no such file can be found, it will simply return an empty string. Notes ----- The location of the actual help file will be found under the task's installed directory using 'irafutils.rglob' to search all sub-dirs to find the file. This allows the help file to be either in the tasks installed directory or in any sub-directory, such as a "help/" directory. Parameters ---------- taskname: string Value of `__taskname__` for a module/task taskpath: string Value of `__file__` for an installed module which defines the task Returns ------- helpString: string multi-line string read from the file '.help' """ #get the local library directory where the code is stored pathsplit=os.path.split(taskpath) # taskpath should be task's __file__ if taskname.find('.') > -1: # if taskname is given as package.taskname... helpname=taskname.split(".")[1] # taskname should be __taskname__ from task's module else: helpname = taskname localdir = pathsplit[0] if localdir == '': localdir = '.' helpfile=rglob(localdir,helpname+".help")[0] if os.access(helpfile,os.R_OK): fh=open(helpfile,'r') ss=fh.readlines() fh.close() helpString="" for line in ss: helpString+=line else: helpString= '' return helpString def cfgGetBool(theObj, name, dflt): """ Get a stringified val from a ConfigObj obj and return it as bool """ strval = theObj.get(name, None) if strval is None: return dflt return strval.lower().strip() == 'true' # Main class class ConfigObjEparDialog(editpar.EditParDialog): # i.e. TEAL """ The TEAL GUI. """ FALSEVALS = (None, False, '', 0, 0.0, '0', '0.0', 'OFF', 'Off', 'off', 'NO', 'No', 'no', 'N', 'n', 'FALSE', 'False', 'false') def __init__(self, theTask, parent=None, title=APP_NAME, isChild=0, childList=None, autoClose=False, strict=False, canExecute=True): # overrides=None, self._do_usac = autoClose # Keep track of any passed-in args before creating the _taskParsObj # self._overrides = overrides self._canExecute = canExecute self._strict = strict # Init base - calls _setTaskParsObj(), sets self.taskName, etc # Note that this calls _overrideMasterSettings() editpar.EditParDialog.__init__(self, theTask, parent, isChild, title, childList, resourceDir=cfgpars.getAppDir()) # We don't return from this until the GUI is closed def _overrideMasterSettings(self): """ Override so that we can run in a different mode. """ # config-obj dict of defaults cod = self._getGuiSettings() # our own GUI setup self._appName = APP_NAME self._appHelpString = tealHelpString self._useSimpleAutoClose = self._do_usac self._showExtraHelpButton = False self._saveAndCloseOnExec = cfgGetBool(cod, 'saveAndCloseOnExec', True) self._showHelpInBrowser = cfgGetBool(cod, 'showHelpInBrowser', False) self._writeProtectOnSaveAs = cfgGetBool(cod, 'writeProtectOnSaveAsOpt', True) self._flagNonDefaultVals = cfgGetBool(cod, 'flagNonDefaultVals', None) self._optFile = APP_NAME.lower()+".optionDB" # our own colors # prmdrss teal: #00ffaa, pure cyan (teal) #00ffff (darker) #008080 # "#aaaaee" is a darker but good blue, but "#bbbbff" pops ltblu = "#ccccff" # light blue drktl = "#008888" # darkish teal self._frmeColor = cod.get('frameColor', drktl) self._taskColor = cod.get('taskBoxColor', ltblu) self._bboxColor = cod.get('buttonBoxColor', ltblu) self._entsColor = cod.get('entriesColor', ltblu) self._flagColor = cod.get('flaggedColor', 'brown') # double check _canExecute, but only if it is still set to the default if self._canExecute and self._taskParsObj: # default _canExecute=True self._canExecute = self._taskParsObj.canExecute() self._showExecuteButton = self._canExecute # check on the help string - just to see if it is HTML # (could use HTMLParser here if need be, be quick and simple tho) hhh = self.getHelpString(self.pkgName+'.'+self.taskName) if hhh: hhh = hhh.lower() if hhh.find('= 0 or hhh.find('') > 0: self._knowTaskHelpIsHtml = True elif hhh.startswith('http:') or hhh.startswith('https:'): self._knowTaskHelpIsHtml = True elif hhh.startswith('file:') and \ (hhh.endswith('.htm') or hhh.endswith('.html')): self._knowTaskHelpIsHtml = True def _preMainLoop(self): """ Override so that we can do some things right before activating. """ # Put the fname in the title. EditParDialog doesn't do this by default self.updateTitle(self._taskParsObj.filename) def _doActualSave(self, fname, comment, set_ro=False, overwriteRO=False): """ Override this so we can handle case of file not writable, as well as to make our _lastSavedState copy. """ self.debug('Saving, file name given: '+str(fname)+', set_ro: '+\ str(set_ro)+', overwriteRO: '+str(overwriteRO)) cantWrite = False inInstArea = False if fname in (None, ''): fname = self._taskParsObj.getFilename() # now do some final checks then save try: if _isInstalled(fname): # check: may be installed but not read-only inInstArea = cantWrite = True else: # in case of save-as, allow overwrite of read-only file if overwriteRO and os.path.exists(fname): setWritePrivs(fname, True, True) # try make writable # do the save rv=self._taskParsObj.saveParList(filename=fname,comment=comment) except IOError: cantWrite = True # User does not have privs to write to this file. Get name of local # choice and try to use that. if cantWrite: fname = self._taskParsObj.getDefaultSaveFilename() # Tell them the context is changing, and where we are saving msg = 'Read-only config file for task "' if inInstArea: msg = 'Installed config file for task "' msg += self._taskParsObj.getName()+'" is not to be overwritten.'+\ ' Values will be saved to: \n\n\t"'+fname+'".' showwarning(message=msg, title="Will not overwrite!") # Try saving to their local copy rv=self._taskParsObj.saveParList(filename=fname, comment=comment) # Treat like a save-as (update title for ALL save ops) self._saveAsPostSave_Hook(fname) # Limit write privs if requested (only if not in the rc dir) if set_ro and os.path.dirname(os.path.abspath(fname)) != \ os.path.abspath(self._rcDir): cfgpars.checkSetReadOnly(fname) # Before returning, make a copy so we know what was last saved. # The dict() method returns a deep-copy dict of the keyvals. self._lastSavedState = self._taskParsObj.dict() return rv def _saveAsPostSave_Hook(self, fnameToBeUsed_UNUSED): """ Override this so we can update the title bar. """ self.updateTitle(self._taskParsObj.filename) # _taskParsObj is correct def hasUnsavedChanges(self): """ Determine if there are any edits in the GUI that have not yet been saved (e.g. to a file). """ # Sanity check - this case shouldn't occur if self._lastSavedState is None: raise RuntimeError("BUG: Please report this as it should never occur.") # Force the current GUI values into our model in memory, but don't # change anything. Don't save to file, don't even convert bad # values to their previous state in the gui. Note that this can # leave the GUI in a half-saved state, but since we are about to exit # this is OK. We only want prompting to occur if they decide to save. badList = self.checkSetSaveEntries(doSave=False, fleeOnBadVals=True, allowGuiChanges=False) if badList: return True # Then compare our data to the last known saved state. MAKE SURE # the LHS is the actual dict (and not 'self') to invoke the dict # comparison only. return self._lastSavedState != self._taskParsObj # Employ an edited callback for a given item? def _defineEditedCallbackObjectFor(self, parScope, parName): """ Override to allow us to use an edited callback. """ # We know that the _taskParsObj is a ConfigObjPars triggerStrs = self._taskParsObj.getTriggerStrings(parScope, parName) # Some items will have a trigger, but likely most won't if triggerStrs and len(triggerStrs) > 0: return self else: return None def _nonStandardEparOptionFor(self, paramTypeStr): """ Override to allow use of TealActionParButton. Return None or a class which derives from EparOption. """ if paramTypeStr == 'z': from . import teal_bttn return teal_bttn.TealActionParButton else: return None def updateTitle(self, atitle): """ Override so we can append read-only status. """ if atitle and os.path.exists(atitle): if _isInstalled(atitle): atitle += ' [installed]' elif not os.access(atitle, os.W_OK): atitle += ' [read only]' super(ConfigObjEparDialog, self).updateTitle(atitle) def edited(self, scope, name, lastSavedVal, newVal, action): """ This is the callback function invoked when an item is edited. This is only called for those items which were previously specified to use this mechanism. We do not turn this on for all items because the performance might be prohibitive. This kicks off any previously registered triggers. """ # Get name(s) of any triggers that this par triggers triggerNamesTup = self._taskParsObj.getTriggerStrings(scope, name) if not triggerNamesTup: raise ValueError('Empty trigger name for: "' + name + '", consult the .cfgspc file.') # Loop through all trigger names - each one is a trigger to kick off - # in the order that they appear in the tuple we got. Most cases will # probably only have a single trigger in the tuple. for triggerName in triggerNamesTup: # First handle the known/canned trigger names # print (scope, name, newVal, action, triggerName) # DBG: debug line # _section_switch_ if triggerName == '_section_switch_': # Try to uniformly handle all possible par types here, not # just boolean (e.g. str, int, float, etc.) # Also, see logic in _BooleanMixin._coerceOneValue() state = newVal not in self.FALSEVALS self._toggleSectionActiveState(scope, state, (name,)) continue # _2_section_switch_ (see notes above in _section_switch_) if triggerName == '_2_section_switch_': state = newVal not in self.FALSEVALS # toggle most of 1st section (as usual) and ALL of next section self._toggleSectionActiveState(scope, state, (name,)) # get first par of next section (fpons) - is a tuple fpons = self.findNextSection(scope, name) nextSectScope = fpons[0] if nextSectScope: self._toggleSectionActiveState(nextSectScope, state, None) continue # Now handle rules with embedded code (eg. triggerName=='_rule1_') if '_RULES_' in self._taskParsObj and \ triggerName in self._taskParsObj['_RULES_'].configspec: # Get codeStr to execute it, but before we do so, check 'when' - # make sure this is an action that is allowed to cause a trigger ruleSig = self._taskParsObj['_RULES_'].configspec[triggerName] chkArgsDict = vtor_checks.sigStrToKwArgsDict(ruleSig) codeStr = chkArgsDict.get('code') # or None if didn't specify when2run = chkArgsDict.get('when') # or None if didn't specify greenlight = False # do we have a green light to eval the rule? if when2run is None: greenlight = True # means run rule for any possible action else: # 'when' was set to something so we need to check action # check value of action (poor man's enum) if action not in editpar.GROUP_ACTIONS: raise ValueError("Unknown action: " + str(action) + ', expected one of: ' + str(editpar.GROUP_ACTIONS)) # check value of 'when' (allow them to use comma-sep'd str) # (readers be aware that values must be those possible for # 'action', and 'always' is also allowed) whenlist = when2run.split(',') # warn for invalid values for w in whenlist: if not w in editpar.GROUP_ACTIONS and w != 'always': print('WARNING: skipping bad value for when kwd: "'+\ w+'" in trigger/rule: '+triggerName) # finally, do the correlation greenlight = 'always' in whenlist or action in whenlist # SECURITY NOTE: because this part executes arbitrary code, that # code string must always be found only in the configspec file, # which is intended to only ever be root-installed w/ the pkg. if codeStr: if not greenlight: continue # not an error, just skip this one self.showStatus("Evaluating "+triggerName+' ...') #dont keep self.top.update_idletasks() #allow msg to draw prior to exec # execute it and retrieve the outcome try: outval = execEmbCode(scope, name, newVal, self, codeStr) except Exception as ex: outval = 'ERROR in '+triggerName+': '+str(ex) print(outval) msg = outval+':\n'+('-'*99)+'\n'+traceback.format_exc() msg += 'CODE: '+codeStr+'\n'+'-'*99+'\n' self.debug(msg) self.showStatus(outval, keep=1) # Leave this debug line in until it annoys someone msg = 'Value of "'+name+'" triggered "'+triggerName+'"' stroutval = str(outval) if len(stroutval) < 30: msg += ' --> "'+stroutval+'"' self.showStatus(msg, keep=0) # Now that we have triggerName evaluated to outval, we need # to look through all the parameters and see if there are # any items to be affected by triggerName (e.g. '_rule1_') self._applyTriggerValue(triggerName, outval) continue # If we get here, we have an unknown/unusable trigger raise RuntimeError('Unknown trigger for: "'+name+'", named: "'+ \ str(triggerName)+'". Please consult the .cfgspc file.') def findNextSection(self, scope, name): """ Starts with given par (scope+name) and looks further down the list of parameters until one of a different non-null scope is found. Upon success, returns the (scope, name) tuple, otherwise (None, None). """ # first find index of starting point plist = self._taskParsObj.getParList() start = 0 for i in range(len(plist)): if scope == plist[i].scope and name == plist[i].name: start = i break else: print('WARNING: could not find starting par: '+scope+'.'+name) return (None, None) # now find first different (non-null) scope in a par, after start for i in range(start, len(plist)): if len(plist[i].scope) > 0 and plist[i].scope != scope: return (plist[i].scope, plist[i].name) # else didn't find it return (None, None) def _setTaskParsObj(self, theTask): """ Overridden version for ConfigObj. theTask can be either a .cfg file name or a ConfigObjPars object. """ # Create the ConfigObjPars obj self._taskParsObj = cfgpars.getObjectFromTaskArg(theTask, self._strict, False) # Tell it that we can be used for catching debug lines self._taskParsObj.setDebugLogger(self) # Immediately make a copy of it's un-tampered internal dict. # The dict() method returns a deep-copy dict of the keyvals. self._lastSavedState = self._taskParsObj.dict() # do this here ??!! or before _lastSavedState ??!! # self._taskParsObj.strictUpdate(self._overrides) def _getSaveAsFilter(self): """ Return a string to be used as the filter arg to the save file dialog during Save-As. """ # figure the dir to use, start with the one from the file absRcDir = os.path.abspath(self._rcDir) thedir = os.path.abspath(os.path.dirname(self._taskParsObj.filename)) # skip if not writeable, or if is _rcDir if thedir == absRcDir or not os.access(thedir, os.W_OK): thedir = os.path.abspath(os.path.curdir) # create save-as filter string filt = thedir+'/*.cfg' envVarName = APP_NAME.upper()+'_CFG' if envVarName in os.environ: upx = os.environ[envVarName] if len(upx) > 0: filt = upx+"/*.cfg" # done return filt def _getOpenChoices(self): """ Go through all possible sites to find applicable .cfg files. Return as an iterable. """ tsk = self._taskParsObj.getName() taskFiles = set() dirsSoFar = [] # this helps speed this up (skip unneeded globs) # last dir aDir = os.path.dirname(self._taskParsObj.filename) if len(aDir) < 1: aDir = os.curdir dirsSoFar.append(aDir) taskFiles.update(cfgpars.getCfgFilesInDirForTask(aDir, tsk)) # current dir aDir = os.getcwd() if aDir not in dirsSoFar: dirsSoFar.append(aDir) taskFiles.update(cfgpars.getCfgFilesInDirForTask(aDir, tsk)) # task's python pkg dir (if tsk == python pkg name) try: x, pkgf = cfgpars.findCfgFileForPkg(tsk, '.cfg', taskName=tsk, pkgObj=self._taskParsObj.getAssocPkg()) taskFiles.update( (pkgf,) ) except cfgpars.NoCfgFileError: pass # no big deal - maybe there is no python package # user's own resourceDir aDir = self._rcDir if aDir not in dirsSoFar: dirsSoFar.append(aDir) taskFiles.update(cfgpars.getCfgFilesInDirForTask(aDir, tsk)) # extra loc - see if they used the app's env. var aDir = dirsSoFar[0] # flag to skip this if no env var found envVarName = APP_NAME.upper()+'_CFG' if envVarName in os.environ: aDir = os.environ[envVarName] if aDir not in dirsSoFar: dirsSoFar.append(aDir) taskFiles.update(cfgpars.getCfgFilesInDirForTask(aDir, tsk)) # At the very end, add an option which we will later interpret to mean # to open the file dialog. taskFiles = list(taskFiles) # so as to keep next item at end of seq taskFiles.sort() taskFiles.append("Other ...") return taskFiles # OPEN: load parameter settings from a user-specified file def pfopen(self, event=None): """ Load the parameter settings from a user-specified file. """ # Get the selected file name fname = self._openMenuChoice.get() # Also allow them to simply find any file - do not check _task_name_... # (could use tkinter's FileDialog, but this one is prettier) if fname[-3:] == '...': if capable.OF_TKFD_IN_EPAR: fname = askopenfilename(title="Load Config File", parent=self.top) else: from . import filedlg fd = filedlg.PersistLoadFileDialog(self.top, "Load Config File", self._getSaveAsFilter()) if fd.Show() != 1: fd.DialogCleanup() return fname = fd.GetFileName() fd.DialogCleanup() if not fname: return # canceled self.debug('Loading from: '+fname) # load it into a tmp object (use associatedPkg if we have one) try: tmpObj = cfgpars.ConfigObjPars(fname, associatedPkg=\ self._taskParsObj.getAssocPkg(), strict=self._strict) except Exception as ex: showerror(message=ex.message, title='Error in '+os.path.basename(fname)) self.debug('Error in '+os.path.basename(fname)) self.debug(traceback.format_exc()) return # check it to make sure it is a match if not self._taskParsObj.isSameTaskAs(tmpObj): msg = 'The current task is "'+self._taskParsObj.getName()+ \ '", but the selected file is for task "'+ \ str(tmpObj.getName())+'". This file was not loaded.' showerror(message=msg, title="Error in "+os.path.basename(fname)) self.debug(msg) self.debug(traceback.format_exc()) return # Set the GUI entries to these values (let the user Save after) newParList = tmpObj.getParList() try: self.setAllEntriesFromParList(newParList, updateModel=True) # go ahead and updateModel, even though it will take longer, # we need it updated for the copy of the dict we make below except editpar.UnfoundParamError as pe: showwarning(message=str(pe), title="Error in "+os.path.basename(fname)) # trip any triggers self.checkAllTriggers('fopen') # This new fname is our current context self.updateTitle(fname) self._taskParsObj.filename = fname # !! maybe try setCurrentContext() ? self.freshenFocus() self.showStatus("Loaded values from: "+fname, keep=2) # Since we are in a new context (and have made no changes yet), make # a copy so we know what the last state was. # The dict() method returns a deep-copy dict of the keyvals. self._lastSavedState = self._taskParsObj.dict() def unlearn(self, event=None): """ Override this so that we can set to default values our way. """ self.debug('Clicked defaults') self._setToDefaults() self.freshenFocus() def _handleParListMismatch(self, probStr, extra=False): """ Override to include ConfigObj filename and specific errors. Note that this only handles "missing" pars and "extra" pars, not wrong-type pars. So it isn't that big of a deal. """ # keep down the duplicate errors if extra: return True # the base class is already stating it will be ignored # find the actual errors, and then add that to the generic message errmsg = 'Warning: ' if self._strict: errmsg = 'ERROR: ' errmsg = errmsg+'mismatch between default and current par lists ' + \ 'for task "'+self.taskName+'".' if probStr: errmsg += '\n\t'+probStr errmsg += '\nTry editing/deleting: "' + \ self._taskParsObj.filename+'").' print(errmsg) return True # as we said, not that big a deal def _setToDefaults(self): """ Load the default parameter settings into the GUI. """ # Create an empty object, where every item is set to it's default value try: tmpObj = cfgpars.ConfigObjPars(self._taskParsObj.filename, associatedPkg=\ self._taskParsObj.getAssocPkg(), setAllToDefaults=self.taskName, strict=False) except Exception as ex: msg = "Error Determining Defaults" showerror(message=msg+'\n\n'+ex.message, title="Error Determining Defaults") return # Set the GUI entries to these values (let the user Save after) tmpObj.filename = self._taskParsObj.filename = '' # name it later newParList = tmpObj.getParList() try: self.setAllEntriesFromParList(newParList) # needn't updateModel yet self.checkAllTriggers('defaults') self.updateTitle('') self.showStatus("Loaded default "+self.taskName+" values via: "+ \ os.path.basename(tmpObj._original_configspec), keep=1) except editpar.UnfoundParamError as pe: showerror(message=str(pe), title="Error Setting to Default Values") def getDict(self): """ Retrieve the current parameter settings from the GUI.""" # We are going to have to return the dict so let's # first make sure all of our models are up to date with the values in # the GUI right now. badList = self.checkSetSaveEntries(doSave=False) if badList: self.processBadEntries(badList, self.taskName, canCancel=False) return self._taskParsObj.dict() def loadDict(self, theDict): """ Load the parameter settings from a given dict into the GUI. """ # We are going to have to merge this info into ourselves so let's # first make sure all of our models are up to date with the values in # the GUI right now. badList = self.checkSetSaveEntries(doSave=False) if badList: if not self.processBadEntries(badList, self.taskName): return # now, self._taskParsObj is up-to-date # So now we update _taskParsObj with the input dict cfgpars.mergeConfigObj(self._taskParsObj, theDict) # now sync the _taskParsObj dict with its par list model # '\n'.join([str(jj) for jj in self._taskParsObj.getParList()]) self._taskParsObj.syncParamList(False) # Set the GUI entries to these values (let the user Save after) try: self.setAllEntriesFromParList(self._taskParsObj.getParList(), updateModel=True) self.checkAllTriggers('fopen') self.freshenFocus() self.showStatus('Loaded '+str(len(theDict))+ \ ' user par values for: '+self.taskName, keep=1) except Exception as ex: showerror(message=ex.message, title="Error Setting to Loaded Values") def _getGuiSettings(self): """ Return a dict (ConfigObj) of all user settings found in rcFile. """ # Put the settings into a ConfigObj dict (don't use a config-spec) rcFile = self._rcDir+os.sep+APP_NAME.lower()+'.cfg' if os.path.exists(rcFile): try: return configobj.ConfigObj(rcFile) except: raise RuntimeError('Error parsing: '+os.path.realpath(rcFile)) # tho, for simple types, unrepr=True eliminates need for .cfgspc # also, if we turn unrepr on, we don't need cfgGetBool else: return {} def _saveGuiSettings(self): """ The base class doesn't implement this, so we will - save settings (only GUI stuff, not task related) to a file. """ # Put the settings into a ConfigObj dict (don't use a config-spec) rcFile = self._rcDir+os.sep+APP_NAME.lower()+'.cfg' # if os.path.exists(rcFile): os.remove(rcFile) co = configobj.ConfigObj(rcFile) # can skip try-block, won't read file co['showHelpInBrowser'] = self._showHelpInBrowser co['saveAndCloseOnExec'] = self._saveAndCloseOnExec co['writeProtectOnSaveAsOpt'] = self._writeProtectOnSaveAs co['flagNonDefaultVals'] = self._flagNonDefaultVals co['frameColor'] = self._frmeColor co['taskBoxColor'] = self._taskColor co['buttonBoxColor'] = self._bboxColor co['entriesColor'] = self._entsColor co['flaggedColor'] = self._flagColor co.initial_comment = ['Automatically generated by '+\ APP_NAME+'. All edits will eventually be overwritten.'] co.initial_comment.append('To use platform default colors, delete each color line below.') co.final_comment = [''] # ensure \n at EOF co.write() def _applyTriggerValue(self, triggerName, outval): """ Here we look through the entire .cfgspc to see if any parameters are affected by this trigger. For those that are, we apply the action to the GUI widget. The action is specified by depType. """ # First find which items are dependent upon this trigger (cached) # e.g. { scope1.name1 : dep'cy-type, scope2.name2 : dep'cy-type, ... } depParsDict = self._taskParsObj.getParsWhoDependOn(triggerName) if not depParsDict: return if 0: print("Dependent parameters:\n"+str(depParsDict)+"\n") # Get model data, the list of pars theParamList = self._taskParsObj.getParList() # Then go through the dependent pars and apply the trigger to them settingMsg = '' for absName in depParsDict: used = False # For each dep par, loop to find the widget for that scope.name for i in range(self.numParams): scopedName = theParamList[i].scope+'.'+theParamList[i].name # diff from makeFullName!! if absName == scopedName: # a match was found depType = depParsDict[absName] if depType == 'active_if': self.entryNo[i].setActiveState(outval) elif depType == 'inactive_if': self.entryNo[i].setActiveState(not outval) elif depType == 'is_set_by': self.entryNo[i].forceValue(outval, noteEdited=True) # WARNING! noteEdited=True may start recursion! if len(settingMsg) > 0: settingMsg += ", " settingMsg += '"'+theParamList[i].name+'" to "'+\ outval+'"' elif depType in ('set_yes_if', 'set_no_if'): if bool(outval): newval = 'yes' if depType == 'set_no_if': newval = 'no' self.entryNo[i].forceValue(newval, noteEdited=True) # WARNING! noteEdited=True may start recursion! if len(settingMsg) > 0: settingMsg += ", " settingMsg += '"'+theParamList[i].name+'" to "'+\ newval+'"' else: if len(settingMsg) > 0: settingMsg += ", " settingMsg += '"'+theParamList[i].name+\ '" (no change)' elif depType == 'is_disabled_by': # this one is only used with boolean types on = self.entryNo[i].convertToNative(outval) if on: # do not activate whole section or change # any values, only activate this one self.entryNo[i].setActiveState(True) else: # for off, set the bool par AND grey WHOLE section self.entryNo[i].forceValue(outval, noteEdited=True) self.entryNo[i].setActiveState(False) # we'd need this if the par had no _section_switch_ # self._toggleSectionActiveState( # theParamList[i].scope, False, None) if len(settingMsg) > 0: settingMsg += ", " settingMsg += '"'+theParamList[i].name+'" to "'+\ outval+'"' else: raise RuntimeError('Unknown dependency: "'+depType+ \ '" for par: "'+scopedName+'"') used = True break # Or maybe it is a whole section if absName.endswith('._section_'): scope = absName[:-10] depType = depParsDict[absName] if depType == 'active_if': self._toggleSectionActiveState(scope, outval, None) elif depType == 'inactive_if': self._toggleSectionActiveState(scope, not outval, None) used = True # Help to debug the .cfgspc rules if not used: raise RuntimeError('UNUSED "'+triggerName+'" dependency: '+ \ str({absName:depParsDict[absName]})) if len(settingMsg) > 0: # why ?! self.freshenFocus() self.showStatus('Automatically set '+settingMsg, keep=1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/teal_bttn.py0000644000175000017500000000535114214070451016174 0ustar00olesoles"""teal_bttn.py: for defining the action "parameter" button widget to be used in TEAL. $Id$ """ import traceback from . import eparoption, vtor_checks class TealActionParButton(eparoption.ActionEparButton): def getButtonLabel(self): """ Return string to be used on as button label - "value" of par. """ # If the value has a comma, return the 2nd part, else use whole thing return self.value.split(',')[-1].strip() def getShowName(self): """ Return string to be used on LHS of button - "name" of par. """ # If the value has a comma, return the 1st part, else leave empty if self.value.find(',') >= 0: return self.value.split(',')[0] else: return '' def flagThisPar(self, currentVal, force): """ Override this to do nothing - the value of this par will never be wrong and thus never need to be flagged. """ pass def clicked(self): """ Called when this button is clicked. Execute code from .cfgspc """ try: from . import teal except: teal = None try: # start drilling down into the tpo to get the code tealGui = self._mainGuiObj tealGui.showStatus('Clicked "'+self.getButtonLabel()+'"', keep=1) pscope = self.paramInfo.scope pname = self.paramInfo.name tpo = tealGui._taskParsObj tup = tpo.getExecuteStrings(pscope, pname) code = '' if not tup: if teal: teal.popUpErr(tealGui.top, "No action to perform", "Action Button Error") return for exname in tup: if '_RULES_' in tpo and exname in tpo['_RULES_'].configspec: ruleSig = tpo['_RULES_'].configspec[exname] chkArgsDict = vtor_checks.sigStrToKwArgsDict(ruleSig) code = chkArgsDict.get('code') # a string or None # now go ahead and execute it teal.execEmbCode(pscope, pname, self.getButtonLabel(), tealGui, code) # done tealGui.debug('Finished: "'+self.getButtonLabel()+'"') except Exception as ex: msg = 'Error executing: {}\n{}"'.format( self.getButtonLabel(), ex.args[0]) msgFull = msg+'\n'+''.join(traceback.format_exc()) msgFull+= "CODE:\n"+code if tealGui: if teal: teal.popUpErr(tealGui.top, msg, "Action Button Error") tealGui.debug(msgFull) else: if teal: teal.popUpErr(None, msg, "Action Button Error") print(msgFull) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.672998 pyraf-2.2.1/pyraf/tools/tests/0000755000175000017500000000000014223000071014773 5ustar00olesoles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/__init__.py0000644000175000017500000000000014214070451017103 0ustar00olesoles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/cfgobj_output.ref0000644000175000017500000003331614214070451020362 0ustar00olesolesTHE CONFIG-OBJ: {'STEP 1: STATIC MASK': {'static': True, 'static_sig': 4.0}, 'STEP 2: SKY SUBTRACTION': {'skyclip': 5, 'skylower': None, 'skylsigma': 4.0, 'skystat': 'median', 'skysub': False, 'skyupper': None, 'skyuser': '', 'skyusigma': 4.0, 'skywidth': None}, 'STEP 3: DRIZZLE SEPARATE IMAGES': {'_section_': '', 'driz_sep_bits': 0, 'driz_sep_fillval': '', 'driz_sep_kernel': 'turbo', 'driz_sep_outnx': None, 'driz_sep_outny': None, 'driz_sep_pixfrac': 1.0, 'driz_sep_rot': None, 'driz_sep_scale': None, 'driz_sep_wt_scl': 'exptime', 'driz_separate': False}, 'STEP 4: CREATE MEDIAN IMAGE': {'combine_grow': 1, 'combine_hthresh': 'INDEF', 'combine_lthresh': 'INDEF', 'combine_maskpt': 0.5, 'combine_nhigh': 0, 'combine_nlow': 0, 'combine_nsigma': '4 3', 'combine_type': 'minmed', 'median': True, 'median_newmasks': True}, 'STEP 5: BLOT BACK THE MEDIAN IMAGE': {'blot': True, 'blot_interp': 'poly5', 'blot_sinscl': 1.0}, 'STEP 6: REMOVE COSMIC RAYS WITH DERIV, DRIZ_CR': {'driz_cr': True, 'driz_cr_corr': False, 'driz_cr_ctegrow': 0, 'driz_cr_grow': 1, 'driz_cr_scale': '1.2 0.7', 'driz_cr_snr': '3.5 3.0'}, 'STEP 7: DRIZZLE FINAL COMBINED IMAGE': {'driz_combine': True, 'final_bits': 0, 'final_fillval': 'INDEF', 'final_kernel': 'square', 'final_outnx': None, 'final_outny': None, 'final_pixfrac': 1.0, 'final_rot': 0.0, 'final_scale': None, 'final_units': 'cps', 'final_wht_type': 'EXP'}, '_RULES_': {'_rule1_': ''}, '_task_name_': 'rt_sample', 'build': False, 'clean': False, 'coeffs': 'header', 'context': True, 'dec': 32.0, 'group': '', 'input': '', 'mdriztab': True, 'mode': 'all', 'output': 'final_drz.fits', 'ra': 8.0, 'refimage': '', 'runfile': 'my_m-drizzle.run', 'shiftfile': '', 'staticfile': '', 'updatewcs': True, 'workinplace': False} THE CONFIG-SPEC: {'STEP 1: STATIC MASK': {'static': "boolean_kw(default=True, triggers='_section_switch_', comment='Create static bad-pixel mask from the data?')", 'static_sig': "float_kw(default=4.0, pos=6, comment='Skip pos-5; Sigma*rms below mode to clip for static mask')"}, 'STEP 2: SKY SUBTRACTION': {'skyclip': "integer_kw(default=5, comment='Number of clipping iterations')", 'skylower': "float_or_none_kw(default=None, comment='Lower limit of usable data for sky (always in electrons)')", 'skylsigma': "float_kw(default=4., comment='Lower side clipping factor (in sigma)')", 'skystat': 'string_kw(default="median", comment=\'Sky correction statistics parameter\')', 'skysub': "boolean_kw(default=True, triggers='_section_switch_', pos=7, comment='Perform sky subtraction?')", 'skyupper': "float_or_none_kw(default=None, comment='Upper limit of usable data for sky (always in electrons)')", 'skyuser': 'string_kw(default="", comment=\'KEYWORD indicating a sky subtraction value if done by user.\')', 'skyusigma': "float_kw(default=4., comment='Upper side clipping factor (in sigma)')", 'skywidth': "float_or_none_kw(default=None, comment='Bin width for sampling sky statistics (in sigma)')"}, 'STEP 3: DRIZZLE SEPARATE IMAGES': {'_section_': 'string_kw(default=\'\', active_if=\'_rule1_\', comment= "en/dis-able whole section")', 'driz_sep_bits': "integer_kw(default=0, comment='Integer mask bit values considered good')", 'driz_sep_fillval': "string_kw(default=None, comment='Value to be assigned to undefined output points')", 'driz_sep_kernel': 'option_kw("turbo","square","point", "gaussian", "tophat", "lanczos3", default="turbo", comment=\'Shape of kernel function\')', 'driz_sep_outnx': 'float_or_none_kw(default=None, comment="Size of separate output frame\'s X-axis (pixels)")', 'driz_sep_outny': 'float_or_none_kw(default=None, comment="Size of separate output frame\'s Y-axis (pixels)")', 'driz_sep_pixfrac': "float_kw(default=1., comment='Linear size of drop in input pixels')", 'driz_sep_rot': 'float_or_none_kw(default=None, comment="Position Angle of drizzled image\'s Y-axis w.r.t. North (degrees)")', 'driz_sep_scale': "float_or_none_kw(default=None, comment='Absolute size of output pixels in arcsec/pixel')", 'driz_sep_wt_scl': 'string_kw(default="exptime", comment=\'Weighting factor for input data image\')', 'driz_separate': "boolean_kw(default=True, triggers='_section_switch_', comment='Drizzle onto separate output images?')"}, 'STEP 4: CREATE MEDIAN IMAGE': {'combine_grow': "integer_kw(default=1, comment='Radius (pixels) for neighbor rejection')", 'combine_hthresh': 'string_kw(default="INDEF", comment=\'Upper threshold for clipping input pixel values\')', 'combine_lthresh': 'string_kw(default="INDEF", comment=\'Lower threshold for clipping input pixel values\')', 'combine_maskpt': "float_kw(default=0.7, comment='Percentage of weight image value below which it is flagged as a bad pixel.')", 'combine_nhigh': "integer_kw(default=0, comment='minmax: Number of high pixels to reject')", 'combine_nlow': "integer_kw(default=0, comment='minmax: Number of low pixels to reject')", 'combine_nsigma': 'string_kw(default="4 3", comment=\'Significance for accepting minimum instead of median\')', 'combine_type': 'option_kw("minmed","median","sum",default="minmed", comment=\'Type of combine operation\')', 'median': "boolean_kw(default=True, triggers='_section_switch_', is_set_by='_rule1_', comment='Create a median image?')", 'median_newmasks': "boolean_kw(default=true, comment='Create new masks when doing the median?')"}, 'STEP 5: BLOT BACK THE MEDIAN IMAGE': {'blot': "boolean_kw(default=true, triggers='_section_switch_', comment='Blot the median back to the input frame?')", 'blot_interp': 'option_kw("poly5","nearest","linear", "poly3", "sinc",default="poly5", comment=\'Interpolant (nearest,linear,poly3,poly5,sinc)\')', 'blot_sinscl': "float_kw(default=1.0, comment='Scale for sinc interpolation kernel')"}, 'STEP 6: REMOVE COSMIC RAYS WITH DERIV, DRIZ_CR': {'driz_cr': "boolean_kw(default=True, triggers='_section_switch_', comment='Perform CR rejection with deriv and driz_cr?')", 'driz_cr_corr': "boolean_kw(default=False, comment='Create CR cleaned _cor file and a _crmask file?')", 'driz_cr_ctegrow': "integer_kw(default=0, comment='Driz_cr_ctegrow parameter')", 'driz_cr_grow': "integer_kw(default=1, comment='Driz_cr_grow parameter')", 'driz_cr_scale': 'string_kw(default="1.2 0.7", comment=\'Driz_cr.scale parameter\')', 'driz_cr_snr': 'string_kw(default="3.5 3.0", comment=\'Driz_cr.SNR parameter\')'}, 'STEP 7: DRIZZLE FINAL COMBINED IMAGE': {'driz_combine': "boolean_kw(default=True, triggers='_section_switch_', comment='Perform final drizzle image combination?')", 'final_bits': "integer_kw(default=0, comment='Integer mask bit values considered good')", 'final_fillval': 'string_kw(default="INDEF", comment=\'Value to be assigned to undefined output points\')', 'final_kernel': 'option_kw("square","point","gaussian","turbo","tophat","lanczos3",default="square", comment=\'Shape of kernel function\')', 'final_outnx': "float_or_none_kw(default=None, comment='Size of FINAL output frame X-axis (pixels)')", 'final_outny': "float_or_none_kw(default=None, comment='Size of FINAL output frame Y-axis (pixels)')", 'final_pixfrac': "float_kw(default=1., comment='Linear size of drop in input pixels')", 'final_rot': 'float_kw(default=0., comment="Position Angle of drizzled image\'s Y-axis w.r.t. North (degrees)")', 'final_scale': "float_or_none_kw(default=None, comment='')", 'final_units': 'option_kw("counts", "cps", default="cps", comment=\'Units for final drizzle image (counts or cps)\')', 'final_wht_type': 'option_kw("EXP","ERR","IVM",default="EXP", comment=\'Type of weighting for final drizzle\')'}, '_RULES_': {'_rule1_': "string_kw(default='', code='from pytools.parseinput import parseinput; OUT = len(parseinput(VAL)[0]) > 1')"}, '_task_name_': "string_kw(default='rt_sample')", 'build': "boolean_kw(default=no, comment='Create multi-extension output file?')", 'clean': "boolean_kw(default=no, active_if='_rule1_', comment='Remove temporary files?')", 'coeffs': 'string_kw(default="header", comment=\'Use header-based distortion coefficients?\')', 'context': "boolean_kw(default=yes, comment='Create context image during final drizzle?')", 'dec': "float_or_none_kw(default=None, pos=3, comment='declination output frame center in decimal degrees')", 'group': 'string_kw(default="", inactive_if=\'_rule1_\', comment=\'Single extension or group to be combined/cleaned\')', 'input': 'string_kw(default="", triggers=\'_rule1_\', comment=\'Input files (name, suffix, or @list)\', pos=0)', 'mdriztab': "boolean_kw(default=True, comment='Use Multidrizzle parameter file specified in header?')", 'mode': 'string_kw(default="all", comment=\'\')', 'output': "string_kw(default='final_drz.fits', comment='Rootname for output drizzled products', pos=1)", 'ra': "float_or_none_kw(default=None, active_if='_R2_', pos=2, comment='right ascension output frame center in decimal degrees')", 'refimage': 'string_kw(default="", comment=\'Reference image from which to obtain a WCS\')', 'runfile': 'string_kw(default="multidrizzle.run", comment=\'File for logging the script commands\', pos=4)', 'shiftfile': 'string_kw(default="", comment=\'Shiftfile name\')', 'staticfile': "string_kw(default='', comment='Name of (optional) input static bad-pixel mask')", 'updatewcs': "boolean_kw(default=yes, comment='Update the WCS keywords?')", 'workinplace': "boolean_kw(default=no, comment='Work on input files in place? (NOT RECOMMENDED)')"} sigStrToKwArgsDict: SIGN: string_kw(default='rt_sample') DICT: {'default': 'rt_sample'} SIGN: boolean_kw(default=no, comment='Create multi-extension output file?') DICT: {'comment': 'Create multi-extension output file?', 'default': 'no'} SIGN: boolean_kw(default=no, active_if='_rule1_', comment='Remove temporary files?') DICT: {'active_if': '_rule1_', 'comment': 'Remove temporary files?', 'default': 'no'} SIGN: string_kw(default="header", comment='Use header-based distortion coefficients?') DICT: {'comment': 'Use header-based distortion coefficients?', 'default': 'header'} SIGN: boolean_kw(default=yes, comment='Create context image during final drizzle?') DICT: {'comment': 'Create context image during final drizzle?', 'default': 'yes'} SIGN: float_or_none_kw(default=None, pos=3, comment='declination output frame center in decimal degrees') DICT: {'comment': 'declination output frame center in decimal degrees', 'default': 'None', 'pos': '3'} SIGN: string_kw(default="", inactive_if='_rule1_', comment='Single extension or group to be combined/cleaned') DICT: {'comment': 'Single extension or group to be combined/cleaned', 'default': '', 'inactive_if': '_rule1_'} SIGN: string_kw(default="", triggers='_rule1_', comment='Input files (name, suffix, or @list)', pos=0) DICT: {'comment': 'Input files (name, suffix, or @list)', 'default': '', 'pos': '0', 'triggers': '_rule1_'} SIGN: boolean_kw(default=True, comment='Use Multidrizzle parameter file specified in header?') DICT: {'comment': 'Use Multidrizzle parameter file specified in header?', 'default': 'True'} SIGN: string_kw(default="all", comment='') DICT: {'comment': '', 'default': 'all'} SIGN: string_kw(default='final_drz.fits', comment='Rootname for output drizzled products', pos=1) DICT: {'comment': 'Rootname for output drizzled products', 'default': 'final_drz.fits', 'pos': '1'} SIGN: float_or_none_kw(default=None, active_if='_R2_', pos=2, comment='right ascension output frame center in decimal degrees') DICT: {'active_if': '_R2_', 'comment': 'right ascension output frame center in decimal degrees', 'default': 'None', 'pos': '2'} SIGN: string_kw(default="", comment='Reference image from which to obtain a WCS') DICT: {'comment': 'Reference image from which to obtain a WCS', 'default': ''} SIGN: string_kw(default="multidrizzle.run", comment='File for logging the script commands', pos=4) DICT: {'comment': 'File for logging the script commands', 'default': 'multidrizzle.run', 'pos': '4'} SIGN: string_kw(default="", comment='Shiftfile name') DICT: {'comment': 'Shiftfile name', 'default': ''} SIGN: string_kw(default='', comment='Name of (optional) input static bad-pixel mask') DICT: {'comment': 'Name of (optional) input static bad-pixel mask', 'default': ''} SIGN: boolean_kw(default=yes, comment='Update the WCS keywords?') DICT: {'comment': 'Update the WCS keywords?', 'default': 'yes'} SIGN: boolean_kw(default=no, comment='Work on input files in place? (NOT RECOMMENDED)') DICT: {'comment': 'Work on input files in place? (NOT RECOMMENDED)', 'default': 'no'} THE POS ARGS: ['', 'final_drz.fits', 8.0, 32.0, 'my_m-drizzle.run', 4.0, False] THE KWD ARGS: {'STEP 1: STATIC MASK': {'static': True}, 'STEP 2: SKY SUBTRACTION': {'skyclip': 5, 'skylower': None, 'skylsigma': 4.0, 'skystat': 'median', 'skyupper': None, 'skyuser': '', 'skyusigma': 4.0, 'skywidth': None}, 'STEP 3: DRIZZLE SEPARATE IMAGES': {'_section_': '', 'driz_sep_bits': 0, 'driz_sep_fillval': '', 'driz_sep_kernel': 'turbo', 'driz_sep_outnx': None, 'driz_sep_outny': None, 'driz_sep_pixfrac': 1.0, 'driz_sep_rot': None, 'driz_sep_scale': None, 'driz_sep_wt_scl': 'exptime', 'driz_separate': False}, 'STEP 4: CREATE MEDIAN IMAGE': {'combine_grow': 1, 'combine_hthresh': 'INDEF', 'combine_lthresh': 'INDEF', 'combine_maskpt': 0.5, 'combine_nhigh': 0, 'combine_nlow': 0, 'combine_nsigma': '4 3', 'combine_type': 'minmed', 'median': True, 'median_newmasks': True}, 'STEP 5: BLOT BACK THE MEDIAN IMAGE': {'blot': True, 'blot_interp': 'poly5', 'blot_sinscl': 1.0}, 'STEP 6: REMOVE COSMIC RAYS WITH DERIV, DRIZ_CR': {'driz_cr': True, 'driz_cr_corr': False, 'driz_cr_ctegrow': 0, 'driz_cr_grow': 1, 'driz_cr_scale': '1.2 0.7', 'driz_cr_snr': '3.5 3.0'}, 'STEP 7: DRIZZLE FINAL COMBINED IMAGE': {'driz_combine': True, 'final_bits': 0, 'final_fillval': 'INDEF', 'final_kernel': 'square', 'final_outnx': None, 'final_outny': None, 'final_pixfrac': 1.0, 'final_rot': 0.0, 'final_scale': None, 'final_units': 'cps', 'final_wht_type': 'EXP'}, 'build': False, 'clean': False, 'coeffs': 'header', 'context': True, 'group': '', 'mdriztab': True, 'mode': 'all', 'refimage': '', 'shiftfile': '', 'staticfile': '', 'updatewcs': True, 'workinplace': False} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/rt_sample.cfg0000644000175000017500000001023614214070451017455 0ustar00olesoles_task_name_ = rt_sample input = ""# Input files (name, suffix, or @list) output = final_drz.fits# Rootname for output drizzled products mdriztab = True# Use Multidrizzle parameter file specified in header? refimage = ""# Reference image from which to obtain a WCS runfile = my_m-drizzle.run# File for logging the script commands workinplace = False# Work on input files in place? (NOT RECOMMENDED) updatewcs = True# Update the WCS keywords? coeffs = header# Use header-based distortion coefficients? context = True# Create context image during final drizzle? clean = False# Remove temporary files? group = ""# Single extension or group to be combined/cleaned ra = 8.0# right ascension output frame center in decimal degrees dec = 32.0# declination output frame center in decimal degrees shiftfile = ""# Shiftfile name staticfile = ""# Name of (optional) input static bad-pixel mask build = False# Create multi-extension output file? mode = all# [STEP 1: STATIC MASK] static = True# Create static bad-pixel mask from the data? static_sig = 4.0 [STEP 2: SKY SUBTRACTION] skysub = False# Perform sky subtraction? skywidth = None# Bin width for sampling sky statistics (in sigma) skystat = median# Sky correction statistics parameter skylower = None# Lower limit of usable data for sky (always in electrons) skyupper = None# Upper limit of usable data for sky (always in electrons) skyclip = 5# Number of clipping iterations skylsigma = 4.0# Lower side clipping factor (in sigma) skyusigma = 4.0# Upper side clipping factor (in sigma) skyuser = ""# KEYWORD indicating a sky subtraction value if done by user. [STEP 3: DRIZZLE SEPARATE IMAGES] driz_separate = False# Drizzle onto separate output images? driz_sep_outnx = None# Size of separate output frame's X-axis (pixels) driz_sep_outny = None# Size of separate output frame's Y-axis (pixels) driz_sep_kernel = turbo# Shape of kernel function driz_sep_wt_scl = exptime# Weighting factor for input data image driz_sep_scale = None# Absolute size of output pixels in arcsec/pixel driz_sep_pixfrac = 1.0# Linear size of drop in input pixels driz_sep_rot = None# Position Angle of drizzled image's Y-axis w.r.t. North (degrees) driz_sep_fillval = ""# Value to be assigned to undefined output points driz_sep_bits = 0# Integer mask bit values considered good [STEP 4: CREATE MEDIAN IMAGE] median = True# Create a median image? median_newmasks = True# Create new masks when doing the median? combine_maskpt = 0.5# Percentage of weight image value below which it is flagged as a bad pixel. combine_type = minmed# Type of combine operation combine_nsigma = 4 3# Significance for accepting minimum instead of median combine_nlow = 0# minmax: Number of low pixels to reject combine_nhigh = 0# minmax: Number of high pixels to reject combine_lthresh = INDEF# Lower threshold for clipping input pixel values combine_hthresh = INDEF# Upper threshold for clipping input pixel values combine_grow = 1# Radius (pixels) for neighbor rejection [STEP 5: BLOT BACK THE MEDIAN IMAGE] blot = True# Blot the median back to the input frame? blot_interp = poly5# Interpolant (nearest,linear,poly3,poly5,sinc) blot_sinscl = 1.0# Scale for sinc interpolation kernel ["STEP 6: REMOVE COSMIC RAYS WITH DERIV, DRIZ_CR"] driz_cr = True# Perform CR rejection with deriv and driz_cr? driz_cr_corr = False# Create CR cleaned _cor file and a _crmask file? driz_cr_snr = 3.5 3.0# Driz_cr.SNR parameter driz_cr_grow = 1# Driz_cr_grow parameter driz_cr_ctegrow = 0# Driz_cr_ctegrow parameter driz_cr_scale = 1.2 0.7# Driz_cr.scale parameter [STEP 7: DRIZZLE FINAL COMBINED IMAGE] driz_combine = True# Perform final drizzle image combination? final_wht_type = EXP# Type of weighting for final drizzle final_outnx = None# Size of FINAL output frame X-axis (pixels) final_outny = None# Size of FINAL output frame Y-axis (pixels) final_kernel = square# Shape of kernel function final_pixfrac = 1.0# Linear size of drop in input pixels final_rot = 0.0# Position Angle of drizzled image's Y-axis w.r.t. North (degrees) final_fillval = INDEF# Value to be assigned to undefined output points final_bits = 0# Integer mask bit values considered good final_units = cps# Units for final drizzle image (counts or cps) final_scale = None# [_RULES_] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/rt_sample.cfgspc0000644000175000017500000001612414214070451020165 0ustar00olesoles_task_name_ = string_kw(default='rt_sample') input = string_kw(default="", triggers='_rule1_', comment='Input files (name, suffix, or @list)', pos=0) output = string_kw(default='final_drz.fits', comment='Rootname for output drizzled products', pos=1) mdriztab = boolean_kw(default=True, comment='Use Multidrizzle parameter file specified in header?') refimage = string_kw(default="", comment='Reference image from which to obtain a WCS') runfile = string_kw(default="multidrizzle.run", comment='File for logging the script commands', pos=4) workinplace = boolean_kw(default=no, comment='Work on input files in place? (NOT RECOMMENDED)') updatewcs = boolean_kw(default=yes, comment='Update the WCS keywords?') coeffs = string_kw(default="header", comment='Use header-based distortion coefficients?') context = boolean_kw(default=yes, comment='Create context image during final drizzle?') clean = boolean_kw(default=no, active_if='_rule1_', comment='Remove temporary files?') group = string_kw(default="", inactive_if='_rule1_', comment='Single extension or group to be combined/cleaned') ra = float_or_none_kw(default=None, active_if='_R2_', pos=2, comment='right ascension output frame center in decimal degrees') dec = float_or_none_kw(default=None, pos=3, comment='declination output frame center in decimal degrees') shiftfile = string_kw(default="", comment='Shiftfile name') staticfile = string_kw(default='', comment='Name of (optional) input static bad-pixel mask') build = boolean_kw(default=no, comment='Create multi-extension output file?') mode = string_kw(default="all", comment='') [STEP 1: STATIC MASK] static = boolean_kw(default=True, triggers='_section_switch_', comment='Create static bad-pixel mask from the data?') static_sig = float_kw(default=4.0, pos=6, comment='Skip pos-5; Sigma*rms below mode to clip for static mask') [STEP 2: SKY SUBTRACTION] skysub = boolean_kw(default=True, triggers='_section_switch_', pos=7, comment='Perform sky subtraction?') skywidth = float_or_none_kw(default=None, comment='Bin width for sampling sky statistics (in sigma)') skystat = string_kw(default="median", comment='Sky correction statistics parameter') skylower = float_or_none_kw(default=None, comment='Lower limit of usable data for sky (always in electrons)') skyupper = float_or_none_kw(default=None, comment='Upper limit of usable data for sky (always in electrons)') skyclip = integer_kw(default=5, comment='Number of clipping iterations') skylsigma = float_kw(default=4., comment='Lower side clipping factor (in sigma)') skyusigma = float_kw(default=4., comment='Upper side clipping factor (in sigma)') skyuser = string_kw(default="", comment='KEYWORD indicating a sky subtraction value if done by user.') [STEP 3: DRIZZLE SEPARATE IMAGES] _section_ = string_kw(default='', active_if='_rule1_', comment= "en/dis-able whole section") driz_separate = boolean_kw(default=True, triggers='_section_switch_', comment='Drizzle onto separate output images?') driz_sep_outnx = float_or_none_kw(default=None, comment="Size of separate output frame's X-axis (pixels)") driz_sep_outny = float_or_none_kw(default=None, comment="Size of separate output frame's Y-axis (pixels)") driz_sep_kernel = option_kw("turbo","square","point", "gaussian", "tophat", "lanczos3", default="turbo", comment='Shape of kernel function') driz_sep_wt_scl = string_kw(default="exptime", comment='Weighting factor for input data image') driz_sep_scale = float_or_none_kw(default=None, comment='Absolute size of output pixels in arcsec/pixel') driz_sep_pixfrac = float_kw(default=1., comment='Linear size of drop in input pixels') driz_sep_rot = float_or_none_kw(default=None, comment="Position Angle of drizzled image's Y-axis w.r.t. North (degrees)") driz_sep_fillval = string_kw(default=None, comment='Value to be assigned to undefined output points') driz_sep_bits = integer_kw(default=0, comment='Integer mask bit values considered good') [STEP 4: CREATE MEDIAN IMAGE] median = boolean_kw(default=True, triggers='_section_switch_', is_set_by='_rule1_', comment='Create a median image?') median_newmasks = boolean_kw(default=true, comment='Create new masks when doing the median?') combine_maskpt = float_kw(default=0.7, comment='Percentage of weight image value below which it is flagged as a bad pixel.') combine_type = option_kw("minmed","median","sum",default="minmed", comment='Type of combine operation') combine_nsigma = string_kw(default="4 3", comment='Significance for accepting minimum instead of median') combine_nlow = integer_kw(default=0, comment='minmax: Number of low pixels to reject') combine_nhigh = integer_kw(default=0, comment='minmax: Number of high pixels to reject') combine_lthresh = string_kw(default="INDEF", comment='Lower threshold for clipping input pixel values') combine_hthresh = string_kw(default="INDEF", comment='Upper threshold for clipping input pixel values') combine_grow = integer_kw(default=1, comment='Radius (pixels) for neighbor rejection') [STEP 5: BLOT BACK THE MEDIAN IMAGE] blot = boolean_kw(default=true, triggers='_section_switch_', comment='Blot the median back to the input frame?') blot_interp = option_kw("poly5","nearest","linear", "poly3", "sinc",default="poly5", comment='Interpolant (nearest,linear,poly3,poly5,sinc)') blot_sinscl = float_kw(default=1.0, comment='Scale for sinc interpolation kernel') [STEP 6: REMOVE COSMIC RAYS WITH DERIV, DRIZ_CR] driz_cr = boolean_kw(default=True, triggers='_section_switch_', comment='Perform CR rejection with deriv and driz_cr?') driz_cr_corr = boolean_kw(default=False, comment='Create CR cleaned _cor file and a _crmask file?') driz_cr_snr = string_kw(default="3.5 3.0", comment='Driz_cr.SNR parameter') driz_cr_grow = integer_kw(default=1, comment='Driz_cr_grow parameter') driz_cr_ctegrow = integer_kw(default=0, comment='Driz_cr_ctegrow parameter') driz_cr_scale = string_kw(default="1.2 0.7", comment='Driz_cr.scale parameter') [STEP 7: DRIZZLE FINAL COMBINED IMAGE] driz_combine = boolean_kw(default=True, triggers='_section_switch_', comment='Perform final drizzle image combination?') final_wht_type = option_kw("EXP","ERR","IVM",default="EXP", comment='Type of weighting for final drizzle') final_outnx = float_or_none_kw(default=None, comment='Size of FINAL output frame X-axis (pixels)') final_outny = float_or_none_kw(default=None, comment='Size of FINAL output frame Y-axis (pixels)') final_kernel = option_kw("square","point","gaussian","turbo","tophat","lanczos3",default="square", comment='Shape of kernel function') final_pixfrac = float_kw(default=1., comment='Linear size of drop in input pixels') final_rot = float_kw(default=0., comment="Position Angle of drizzled image's Y-axis w.r.t. North (degrees)") final_fillval = string_kw(default="INDEF", comment='Value to be assigned to undefined output points') final_bits = integer_kw(default=0, comment='Integer mask bit values considered good') final_units = option_kw("counts", "cps", default="cps", comment='Units for final drizzle image (counts or cps)') final_scale = float_or_none_kw(default=None, comment='') [_RULES_] # Special strings here are 'OUT' and 'VAL'. _rule1_ = string_kw(default='', code='from pytools.parseinput import parseinput; OUT = len(parseinput(VAL)[0]) > 1') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/test_cfgobj.py0000644000175000017500000000334514214070451017654 0ustar00olesoles"""This was pytools/utils/cfgobj in pandokia.""" import io as StringIO import os import pprint from .. import teal, vtor_checks def test_teal_vtor(tmpdir): data_dir = os.path.dirname(__file__) co = teal.load(os.path.join(data_dir, 'rt_sample.cfg')) f = tmpdir.join('output.txt') # TEST OBJ LOADING f.write("THE CONFIG-OBJ:\n") pprint.pprint(co.dict(), stream=f, indent=3, width=999) # TEST UNDERSTANDING OF .cfgspc cs = co.configspec f.write("\nTHE CONFIG-SPEC:\n") pprint.pprint(cs.dict(), stream=f, indent=3, width=999) # TEST sigStrToKwArgsDict f.write("\nsigStrToKwArgsDict:\n") for item in sorted(cs.keys()): sig = cs[item] if isinstance(sig, str): f.write("SIGN: " + sig + "\n") ddd = vtor_checks.sigStrToKwArgsDict(sig) sss = StringIO.StringIO() # use pprint (and StringIO) so as to print it sorted pprint.pprint(ddd, sss, width=999) f.write("DICT: " + sss.getvalue()) # sss has newline sss.close() # TEST getPosArgs and getKwdArgs f.write("\nTHE POS ARGS:\n") f.write(str(co.getPosArgs()) + "\n") f.write("\nTHE KWD ARGS:\n") pprint.pprint(co.getKwdArgs(), stream=f, indent=3, width=999) lines = f.readlines() stripped = [l.replace(' ', ' ').replace(' ', ' ').replace('{ ', '{').replace(' }', '}').strip() for l in lines] lines = [l for l in stripped if len(l) > 0] with open(os.path.join(data_dir, 'cfgobj_output.ref')) as fref: ans = fref.readlines() bad_lines = [] for x, y in zip(lines, ans): if x != y: bad_lines.append('{} : {}'.format(x, y)) if len(bad_lines) > 0: raise AssertionError(bad_lines) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/test_compmixin.py0000644000175000017500000001026714214070451020426 0ustar00olesolesimport pytest from ..compmixin import ComparableMixin class SimpleStr(ComparableMixin): def __init__(self, v): self.val = str(v) # all input turned to string def __str__(self): return str(self.val) def _cmpkey(self): return self.val class AnyType(ComparableMixin): def __init__(self, v): self.val = v # leave all input typed as is def __str__(self): return str(self.val) # define this instead of _cmpkey - handle ALL sorts of scenarios, # except intentionally don't compare self strings (strlen>1) with integers # so we have a case which fails in our test below def _compare(self, other, method): if isinstance(other, self.__class__): # recurse, get 2 logic below return self._compare(other.val, method) if isinstance(other, str): return method(str(self.val), other) elif other is None and self.val is None: return method(0, 0) elif other is None: # coerce to str compare return method(str(self.val), '') elif isinstance(other, int): # handle ONLY case where self.val is a single char or an int if isinstance(self.val, str) and len(self.val) == 1: return method(ord(self.val), other) else: # assume we are int-like return method(int(self.val), other) try: return method(self.val, other) except (AttributeError, TypeError): return NotImplemented def test_SimpleStr(): a = SimpleStr('a') b = SimpleStr('b') c = SimpleStr('c') two = SimpleStr(2) # compare two SimpleStr objects assert str(a > b) == "False" assert str(a < b) == "True" assert str(a <= b) == "True" assert str(a == b) == "False" assert str(b == b) == "True" assert str(a < c) == "True" assert str(a <= c) == "True" assert str(a != c) == "True" assert str(c != c) == "False" assert str(c == c) == "True" assert str(b < two) == "False" assert str(b >= two) == "True" assert str(b == two) == "False" assert str([str(jj) for jj in sorted([b, a, two, c])] ) == "['2', 'a', 'b', 'c']" def test_AnyType(): x = AnyType('x') y = AnyType('yyy') z = AnyType(0) nn = AnyType(None) # compare two AnyType objects assert str(x > y) == "False" assert str(x < y) == "True" assert str(x <= y) == "True" assert str(x == y) == "False" assert str(y == y) == "True" assert str(x < z) == "False" assert str(x <= z) == "False" assert str(x > z) == "True" assert str(x != z) == "True" assert str(z != z) == "False" assert str(z == z) == "True" assert str(y < nn) == "False" assert str(y >= nn) == "True" assert str(y == nn) == "False" assert str(nn == nn) == "True" assert str([str(jj) for jj in sorted([y, x, nn, z])] ) == "['None', '0', 'x', 'yyy']" # compare AnyType objects to built-in types assert str(x < 0) == "False" assert str(x <= 0) == "False" assert str(x > 0) == "True" assert str(x != 0) == "True" assert str(x == 0) == "False" assert str(x < None) == "False" assert str(x <= None) == "False" assert str(x > None) == "True" assert str(x != None) == "True" assert str(x == None) == "False" assert str(x < "abc") == "False" assert str(x <= "abc") == "False" assert str(x > "abc") == "True" assert str(x != "abc") == "True" assert str(x == "abc") == "False" assert str(y < None) == "False" assert str(y <= None) == "False" assert str(y > None) == "True" assert str(y != None) == "True" assert str(y == None) == "False" assert str(y < "abc") == "False" assert str(y <= "abc") == "False" assert str(y > "abc") == "True" assert str(y != "abc") == "True" assert str(y == "abc") == "False" def test_raise_on_integer_comparison(): y = AnyType('yyy') z = AnyType(0) with pytest.raises(ValueError): y == z # AnyType intentionally doesn't compare strlen>1 to ints def test_raise_on_sort(): y = AnyType('yyy') z = AnyType(0) with pytest.raises(ValueError): sorted([z, y]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/test_irafutils.py0000644000175000017500000000171614214070451020424 0ustar00olesolesimport pytest from ..irafutils import csvSplit @pytest.mark.parametrize('input_str,input_len,expected', [ (None, 0, "[]"), ('', 0, "[]"), (' ', 1, "[' ']"), ('a', 1, "['a']"), (',', 2, "['', '']"), (',a', 2, "['', 'a']"), ('a,', 2, "['a', '']"), (',a,', 3, "['', 'a', '']"), ("abc'-hi,ya-'xyz", 1, """["abc'-hi,ya-'xyz"]"""), ('abc"double-quote,eg"xy,z', 2, """['abc"double-quote,eg"xy', 'z']"""), ('abc"""triple-quote,eg"""xyz', 1, '[\'abc"""triple-quote,eg"""xyz\']'), ("'s1', 'has, comma', z", 3, """["'s1'", " 'has, comma'", ' z']"""), ("a='s1', b='has,comma,s', c", 3, """["a='s1'", " b='has,comma,s'", ' c']"""), ]) def test_csvSplit(input_str, input_len, expected): result = csvSplit(input_str, ',', True) result_len = len(result) assert result_len == input_len and repr(result) == expected, \ "For case: {} expected:\n{}\nbut got:\n{}".format( input_str, expected, repr(result)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tests/test_minmatch.py0000644000175000017500000000367414214070451020227 0ustar00olesolesimport pytest from ..minmatch import MinMatchDict, AmbiguousKeyError BASEKEYS = tuple(['test', 'text', 'ten']) BASEVALUES = tuple([1, 2, 10]) @pytest.fixture def mmd(): d = MinMatchDict() for value in zip(*[BASEKEYS, BASEVALUES]): d.add(*value) return d def test_ambiguous_assignment_key(mmd): with pytest.raises(AmbiguousKeyError): mmd['te'] = 5 def test_ambiguous_assignment_get_t(mmd): with pytest.raises(AmbiguousKeyError): mmd.get('t') def test_ambiguous_assignment_get_tes(mmd): with pytest.raises(AmbiguousKeyError): mmd.get('te') def test_ambiguous_assignment_del_tes(mmd): with pytest.raises(AmbiguousKeyError): del mmd['te'] def test_invalid_key_assignment(mmd): with pytest.raises(KeyError): mmd['t'] def test_dict_sort(mmd): result = [key for key, _ in sorted(mmd.items())] assert result[0] == 'ten' assert result[-1] == 'text' @pytest.mark.parametrize('key', BASEKEYS) def test_get_values(mmd, key): assert mmd.get(key) def test_missing_key_returns_none(mmd): assert mmd.get('teq') is None def test_getall(mmd): return mmd.getall('t') def test_getall_returns_expected_values(mmd): result = mmd.getall('t') assert sorted(result) == [x for x in BASEVALUES] def test_del_key(mmd): del mmd['test'] def test_del_keys(mmd): for key in BASEKEYS: del mmd[key] def test_clear_dict(mmd): mmd.clear() assert mmd == dict() def test_has_key(mmd): for key in BASEKEYS: # Ditch last character in string if len(key) > 3: key = key[:-1] assert mmd.has_key(key, exact=False) def test_has_key_exact(mmd): for key in BASEKEYS: assert mmd.has_key(key, exact=True) def test_key_in_dict(mmd): for key in BASEKEYS: assert key in mmd def test_update_dict(mmd): new_dict = dict(ab=0) mmd.update(new_dict) assert 'test' in mmd and 'ab' in mmd ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/tkrotext.py0000644000175000017500000000450314214070451016102 0ustar00olesoles""" Read-Only tkinter Text Widget. This is a variation of the tkinter Text widget in that the text itself is not editable (it is read-only), but it allows selection for cut/paste to other apps. Cut-paste may currently only work under X11. (9/2015 enabled under OSX by adding 'c' to ALLOWED_SYMS) A vastly simpler way of doing this is to use a tkinter.Text widget and set it to DISABLED, but then you cannot select text. $Id$ """ import sys import tkinter as TKNTR ALLOWED_SYMS = ('c', 'Up', 'Down', 'Left', 'Right', 'Home', 'End', 'Prior', 'Next', 'Shift_L', 'Shift_R') class ROText(TKNTR.Text): def __init__(self, master, **kw): """ defer most of __init__ to the base class """ self._fbto = None if 'focusBackTo' in kw: self._fbto = kw['focusBackTo'] del kw['focusBackTo'] TKNTR.Text.__init__(self, master, **kw) # override some bindings to return a "break" string self.bind("", self.ignoreMostKeys) self.bind("", lambda e: "break") self.bind("", lambda e: "break") if self._fbto: self.bind("", self.mouseLeft) self.config(insertwidth=0) # disallow common insert calls, but allow a backdoor when needed def insert(self, index, text, *tags, **kw): if 'force' in kw and kw['force']: TKNTR.Text.insert(self, index, text, *tags) # disallow common delete calls, but allow a backdoor when needed def delete(self, start, end=None, force=False): if force: TKNTR.Text.delete(self, start, end) # a way to disable text manip def ignoreMostKeys(self, event): if event.keysym not in ALLOWED_SYMS: return "break" # have to return this string to stop the event # To get copy/paste working on OSX we allow 'c' so that # they can type 'Command-c', but don't let a regular 'c' through. if event.keysym in ('c', 'C'): if (sys.platform == 'darwin' and hasattr(event, 'state') and event.state != 0): pass # allow this through, it is Command-c else: return "break" def mouseLeft(self, event): if self._fbto: self._fbto.focus_set() return "break" # have to return this string to stop the event ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/tools/vtor_checks.py0000644000175000017500000001267614214070451016542 0ustar00olesoles#!/usr/local/bin/python """ This file holds our own over-rides for the standard Validator check functions. We over-ride them so that we may add our own special keywords to them in the config_spec. $Id$ """ import configobj import validate from . import irafutils STANDARD_KEYS = ['min', 'max', 'missing', 'default'] OVCDBG = False def sigStrToKwArgsDict(checkFuncSig): """ Take a check function signature (string), and parse it to get a dict of the keyword args and their values. """ p1 = checkFuncSig.find('(') p2 = checkFuncSig.rfind(')') if p1 <= 0 or p2 <= 0 or p2 <= p1: raise ValueError("Invalid signature: " + checkFuncSig) argParts = irafutils.csvSplit(checkFuncSig[p1+1:p2], ',', True) argParts = [x.strip() for x in argParts] retval = {} for argPair in argParts: argSpl = argPair.split('=', 1) if len(argSpl) > 1: if argSpl[0] in retval: if isinstance(retval[argSpl[0]], (list,tuple)): retval[argSpl[0]]+=(irafutils.stripQuotes(argSpl[1]),) # 3rd else: # 2nd in, so convert to tuple retval[argSpl[0]] = (retval[argSpl[0]], irafutils.stripQuotes(argSpl[1]),) else: retval[argSpl[0]] = irafutils.stripQuotes(argSpl[1]) # 1st in else: retval[argSpl[0]] = None # eg. found "triggers=, max=6, ..." return retval def separateKeywords(kwArgsDict): """ Look through the keywords passed and separate the special ones we have added from the legal/standard ones. Return both sets as two dicts (in a tuple), as (standardKws, ourKws) """ standardKws = {} ourKws = {} for k in kwArgsDict: if k in STANDARD_KEYS: standardKws[k]=kwArgsDict[k] else: ourKws[k]=kwArgsDict[k] return (standardKws, ourKws) def addKwdArgsToSig(sigStr, kwArgsDict): """ Alter the passed function signature string to add the given kewords """ retval = sigStr if len(kwArgsDict) > 0: retval = retval.strip(' ,)') # open up the r.h.s. for more args for k in kwArgsDict: if retval[-1] != '(': retval += ", " retval += str(k)+"="+str(kwArgsDict[k]) retval += ')' retval = retval return retval def boolean_check_kw(val, *args, **kw): if OVCDBG: print("boolean_kw for: "+str(val)+", args: "+str(args)+", kw: "+str(kw)) vtor = validate.Validator() checkFuncStr = "boolean"+str(tuple(args)) checkFuncStr = addKwdArgsToSig(checkFuncStr, separateKeywords(kw)[0]) if OVCDBG: print("CFS: "+checkFuncStr+'\n') return vtor.check(checkFuncStr, val) def option_check_kw(val, *args, **kw): if OVCDBG: print("option_kw for: "+str(val)+", args: "+str(args)+", kw: "+str(kw)) vtor = validate.Validator() checkFuncStr = "option"+str(tuple(args)) checkFuncStr = addKwdArgsToSig(checkFuncStr, separateKeywords(kw)[0]) if OVCDBG: print("CFS: "+checkFuncStr+'\n') return vtor.check(checkFuncStr, val) def integer_check_kw(val, *args, **kw): if OVCDBG: print("integer_kw for: "+str(val)+", args: "+str(args)+", kw: "+str(kw)) vtor = validate.Validator() checkFuncStr = "integer"+str(tuple(args)) checkFuncStr = addKwdArgsToSig(checkFuncStr, separateKeywords(kw)[0]) if OVCDBG: print("CFS: "+checkFuncStr+'\n') return vtor.check(checkFuncStr, val) def integer_or_none_check_kw(val, *args, **kw): if OVCDBG: print("integer_or_none_kw for: "+str(val)+", args: "+str(args)+", kw: "+str(kw)) if val in (None,'','None','NONE','INDEF'): return None # only difference vtor = validate.Validator() checkFuncStr = "integer"+str(tuple(args)) checkFuncStr = addKwdArgsToSig(checkFuncStr, separateKeywords(kw)[0]) if OVCDBG: print("CFS: "+checkFuncStr+'\n') return vtor.check(checkFuncStr, val) def float_check_kw(val, *args, **kw): if OVCDBG: print("float_kw for: "+str(val)+", args: "+str(args)+", kw: "+str(kw)) vtor = validate.Validator() checkFuncStr = "float"+str(tuple(args)) checkFuncStr = addKwdArgsToSig(checkFuncStr, separateKeywords(kw)[0]) if OVCDBG: print("CFS: "+checkFuncStr+'\n') return vtor.check(checkFuncStr, val) def float_or_none_check_kw(val, *args, **kw): if OVCDBG: print("float_or_none_kw for: "+str(val)+", args: "+str(args)+", kw: "+str(kw)) if val in (None,'','None','NONE','INDEF'): return None # only difference vtor = validate.Validator() checkFuncStr = "float"+str(tuple(args)) checkFuncStr = addKwdArgsToSig(checkFuncStr, separateKeywords(kw)[0]) if OVCDBG: print("CFS: "+checkFuncStr+'\n') return vtor.check(checkFuncStr, val) def string_check_kw(val, *args, **kw): if OVCDBG: print("string_kw for: "+str(val)+", args: "+str(args)+", kw: "+str(kw)) vtor = validate.Validator() checkFuncStr = "string"+str(tuple(args)) checkFuncStr = addKwdArgsToSig(checkFuncStr, separateKeywords(kw)[0]) if OVCDBG: print("CFS: "+checkFuncStr+'\n') return vtor.check(checkFuncStr, val) FUNC_DICT = {'boolean_kw': boolean_check_kw, 'option_kw': option_check_kw, 'integer_kw': integer_check_kw, 'integer_or_none_kw': integer_or_none_check_kw, 'float_kw': float_check_kw, 'float_or_none_kw': float_or_none_check_kw, 'string_kw': string_check_kw, 'action_kw': string_check_kw } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1646897637.0 pyraf-2.2.1/pyraf/tpar.py0000644000175000017500000013164014212324745014035 0ustar00olesoles"""module 'tpar.py' -- main module for generating the tpar task editor tpar is curses based parameter editing similar to epar. Tpar has the primary goal of simplicity similar to IRAF's CL epar and as such is missing many PyRAF epar features. The primary advantage of tpar is that it works in a simple terminal window (rather than requiring full X-11 and Tk); this is an improvement for low bandwidth network contexts or for people who prefer text interfaces to GUIs. Todd Miller, 2006 May 30 derived from epar.py and IRAF CL epar. """ # XXXX Debugging tip: uncomment self.inform() in the debug() method below import os import sys import re # Fake out import of urwid if it fails, to keep tpar from bringing down # all of PyRAF. class FakeModule: pass class FakeClass: pass try: import urwid.curses_display import urwid.raw_display import urwid from . import urwutil from . import urwfiledlg urwid.set_encoding("ascii") # gives better performance than 'utf8' except ImportException as e: urwid = FakeModule() urwid.Edit = FakeClass() urwid.Columns = FakeClass() urwid.AttrWrap = FakeClass() urwid.Pile = FakeClass() urwid.the_error = str(e) # PyRAF modules from . import iraf from . import irafpar from . import irafhelp from . import iraftask from . import iraffunctions TPAR_HELP_EMACS = """ EDIT COMMANDS (emacs) DEL_CHAR = DEL MOVE_RIGHT = RIGHT_ARROW DEL_LEFT = ^H_or_BS MOVE_RIGHT = ^F DEL_LINE = ^K MOVE_START = ESC-< DEL_WORD = ESC-D MOVE_UP = UP_ARROW DEL_WORD = ESC-d MOVE_UP = ^P EXIT_NOUPD = ^C NEXT_PAGE = ^V EXIT_UPDAT = ^D NEXT_WORD = ESC-F NEXT_WORD = ESC-f GET_HELP = ESC-? PREV_PAGE = ESC-V MOVE_BOL = ^A PREV_PAGE = ESC-v MOVE_DOWN = DOWN_ARROW PREV_WORD = ESC-B MOVE_DOWN = ^N PREV_WORD = ESC-b MOVE_END = ESC-> REPAINT = ^L MOVE_EOL = ^E UNDEL_CHAR = ESC-^D MOVE_LEFT = LEFT_ARROW UNDEL_LINE = ESC-^K MOVE_LEFT = ^B UNDEL_WORD = ESC-^W X-11 Paste: hold down shift and click middle mouse button :e[!] [pset] edit pset "!" == no update :q[!] exit tpar "!" == no update :r! unlearn :w[!] [pset] unsupported :g[!] run task """ TPAR_BINDINGS_EMACS = { "ctrl c": "quit", "ctrl C": "quit", "ctrl d": "exit", "ctrl D": "exit", "ctrl z": "exit", "ctrl Z": "exit", "ctrl p": "up", "ctrl P": "up", "shift tab": "up", "ctrl n": "down", "ctrl N": "down", "esc v": "page down", "esc V": "page down", "esc p": "page up", "esc P": "page up", # "ctrl l" : "redraw", # re-draw... just ignore # "ctrl L" : "redraw", "ctrl K": "del_line", "ctrl k": "del_line", "esc d": "del_word", "esc D": "del_word", "esc f": "next_word", "esc F": "next_word", "esc b": "prev_word", "esc B": "prev_word", "ctrl a": "move_bol", "ctrl A": "move_bol", "ctrl e": "move_eol", "ctrl E": "move_eol", "esc >": "end", "esc <": "home", "ctrl f": "right", "ctrl F": "right", "ctrl b": "left", "ctrl B": "left", "esc ctrl d": "undel_char", "esc ctrl k": "undel_line", "ctrl y": "undel_line", "esc ctrl w": "undel_word", "esc ?": "help" } TPAR_HELP_VI = """ EDIT COMMANDS (vi) DEL_CHAR = BACKSPACE MOVE_LEFT = ^H DEL_LEFT = DEL MOVE_RIGHT = RIGHT_ARROW DEL_LINE = ^I^D MOVE_RIGHT = ^L DEL_WORD = ^I^W MOVE_START = ^T^S EXIT_NOUPD = ^C MOVE_UP = UP_ARROW EXIT_UPDAT = ^D MOVE_UP = ^K EXIT_UPDAT = ^Z NEXT_PAGE = ^N GET_HELP = ESC-? NEXT_WORD = ^W MOVE_BOL = ^A PREV_PAGE = ^P MOVE_DOWN = DOWN_ARROW PREV_WORD = ^B MOVE_DOWN = ^J REPAINT = ^R MOVE_END = ^T^E UNDEL_CHAR = ^U^C MOVE_EOL = ^E UNDEL_LINE = ^U^L MOVE_LEFT = LEFT_ARROW UNDEL_WORD = ^U^W X-11 Paste: hold down shift and click middle mouse button :e[!] [pset] edit pset "!" == no update :q[!] exit tpar "!" == no update :r! unlearn :w[!] [pset] unsupported :g[!] run task """ TPAR_BINDINGS_VI = { "ctrl c": "quit", "ctrl d": "exit", "ctrl C": "quit", "ctrl D": "exit", "ctrl K": "up", "ctrl k": "up", "ctrl j": "down", "ctrl J": "down", "ctrl n": "page down", "ctrl N": "page down", "ctrl p": "page up", "ctrl P": "page up", # "ctrl r" : "redraw", # re-draw... just ignore # "ctrl R" : "redraw", "tab ctrl D": "del_line", "tab ctrl d": "del_line", "tab ctrl W": "del_word", "tab ctrl w": "del_word", "ctrl w": "next_word", "ctrl W": "next_word", "ctrl b": "prev_word", "ctrl B": "prev_word", "ctrl a": "move_bol", "ctrl A": "move_bol", "ctrl e": "move_eol", "ctrl E": "move_eol", "ctrl T ctrl E": "end", "ctrl t ctrl e": "end", "ctrl T ctrl S": "home", "ctrl t ctrl s": "home", "ctrl L": "right", "ctrl l": "right", "ctrl H": "left", "ctrl h": "left", "ctrl U ctrl C": "undel_char", "ctrl u ctrl c": "undel_char", "ctrl U ctrl L": "undel_line", "ctrl u ctrl l": "undel_line", "ctrl U ctrl W": "undel_word", "ctrl u ctrl w": "undel_word", "esc ?": "help" } class Binder: """The Binder class manages keypresses for urwid and adds the ability to bind specific inputs to actions. """ def __init__(self, bindings, inform, mode_keys=[]): self.bindings = bindings self.inform = inform self.mode_keys = mode_keys self.chord = [] def bind(self, k, f): self.bindings[k] = f def keypress(self, pos, key): if key is None: return # Handle the "ready" binding specially to keep the rest simple. if key == "ready": if "ready" in self.bindings: return self.bindings["ready"]() else: return "ready" self.debug(f"pos: {pos} key: {key}") if key in self.mode_keys: self.chord.append(key) return None elif not urwid.is_mouse_event(key): key = " ".join(self.chord + [key]) self.chord = [] visited = [] while key in self.bindings and key not in visited: visited.append(key) f = self.bindings[key] if f is None: key = None elif isinstance(f, str): # str & unicode? key = f else: key = f() self.debug(f"pos: {pos} visited: {' --> '.join(visited)} " f"key: {key} mapping: {f}") return key def debug(self, s): # return self.inform(s) return None class PyrafEdit(urwid.Edit): """PyrafEdit is a text entry widget which has keybindings similar to IRAF's CL epar command. """ def __init__(self, *args, **keys): inform = keys["inform"] del keys["inform"] self.reset_del_buffers() urwid.Edit.__init__(self, *args, **keys) EDIT_BINDINGS = { # single field bindings "delete": self.DEL_CHAR, "del_line": self.DEL_LINE, "del_word": self.DEL_WORD, "undel_char": self.UNDEL_CHAR, "undel_word": self.UNDEL_WORD, "undel_line": self.UNDEL_LINE, "next_word": self.NEXT_WORD, "prev_word": self.PREV_WORD, "move_bol": self.MOVE_BOL, "move_eol": self.MOVE_EOL, "right": self.MOVE_RIGHT, "left": self.MOVE_LEFT, } self._binder = Binder(EDIT_BINDINGS, inform) def reset_del_buffers(self): self._del_words = [] self._del_lines = [] self._del_chars = [] def DEL_CHAR(self): s = self.get_edit_text() if len(s): n = self.edit_pos if n >= len(s): n -= 1 c = s[n] self.set_edit_text(s[:n] + s[n + 1:]) self._del_chars.append(c) def DEL_WORD(self): s = self.get_edit_text() i = self.edit_pos while i > 0 and not s[i].isspace(): i -= 1 if s[i].isspace(): i += 1 word = "" while i < len(s) and not s[i].isspace(): word += s[i] i += 1 s = s[:i - len(word)] + s[i:] self._del_words.append(word) self.edit_pos = i self.set_edit_text(s) def DEL_LINE(self): s = self.get_edit_text() line = s[self.edit_pos:] self.set_edit_text(s[:self.edit_pos]) self.set_edit_pos(len(self.get_edit_text())) self._del_lines.append(line) def NEXT_WORD(self): s = self.get_edit_text() i = self.edit_pos while s and i < len(s) - 1 and not s[i].isspace(): i += 1 while s and i < len(s) - 1 and s[i].isspace(): i += 1 self.edit_pos = i def PREV_WORD(self): s = self.get_edit_text() i = self.edit_pos while s and i > 0 and s[i].isspace(): i -= 1 while s and i > 0 and not s[i].isspace(): i -= 1 self.edit_pos = i def MOVE_BOL(self): self.edit_pos = 0 def MOVE_EOL(self): self.edit_pos = len(self.get_edit_text()) def MOVE_RIGHT(self): if self.edit_pos < len(self.get_edit_text()): self.edit_pos += 1 def MOVE_LEFT(self): if self.edit_pos > 0: self.edit_pos -= 1 def UNDEL_CHAR(self): try: char = self._del_chars.pop() except: return self.insert_text(char) self.edit_pos -= 1 def UNDEL_WORD(self): try: word = self._del_words.pop() except: return self.insert_text(word) def UNDEL_LINE(self): try: if len(self._del_lines) > 1: line = self._del_lines.pop() else: line = self._del_lines[0] except: return self.insert_text(line) def keypress(self, pos, key): key = Binder.keypress(self._binder, pos, key) if key is not None and not urwid.is_mouse_event(key): key = urwid.Edit.keypress(self, pos, key) return key def get_result(self): return self.get_edit_text().strip() def verify(self): return True class StringTparOption(urwid.Columns): def __init__(self, paramInfo, defaultParamInfo, inform): MODE_KEYS = [] BINDINGS = { "enter": self.ENTER, "up": self.MOVE_UP, "down": self.MOVE_DOWN, "page up": self.PAGE_UP, "page down": self.PAGE_DOWN, "undel_line": self.UNDEL_LINE, "ready": self.READY_LINE, "end": self.MOVE_END, "home": self.MOVE_START } self._binder = Binder(BINDINGS, inform, MODE_KEYS) self._mode = "clear" self._newline = True self.inform = inform self.paramInfo = paramInfo self.defaultParamInfo = defaultParamInfo name = self.paramInfo.name value = self.paramInfo.get(field="p_filename", native=0, prompt=0) self._previousValue = value # Generate the input label if (self.paramInfo.get(field="p_mode") == "h"): required = False else: required = True help = self.paramInfo.get(field="p_prompt", native=0, prompt=0) self._args = (name, value, help, required) if not required: name = "(" + name help = ") " + help else: help = " " + help self._name = urwid.Text(f"{name:<10s}=") self._edit = PyrafEdit("", "", wrap="clip", align="right", inform=inform) self._edit.verify = self.verify self._value = urwid.Text(f"{value:10s}", align="right") self._help = urwid.Text(f"{help:<30s}") urwid.Columns.__init__(self, [('weight', 0.20, self._name), ('weight', 0.20, self._edit), ('weight', 0.20, self._value), ('weight', 0.40, self._help)], 0, 1, 1) def keypress(self, pos, key): key = Binder.keypress(self._binder, pos, key) if key: key = self._edit.keypress(pos, key) return key def get_name(self): return self._args[0] def get_candidate(self): return self._edit.get_edit_text() def set_candidate(self, s): self._edit.set_edit_text(s) self._edit.edit_pos = len(s) def normalize(self, v): """abstract method called to standardize equivalent values when the 'result' is set.""" return v def get_result(self): return self._value.get_text()[0].strip() def set_result(self, r): self._value.set_text(self.normalize(str(r))) def unlearn_value(self): self.set_result(self._previousValue) def verify(self, v): self.inform("") return True def UNDEL_LINE( self ): # a little iffy. handle first copy from value field to edit field here. defer subsequent calls. v = self.get_result() if v: self.set_candidate(self.get_candidate() + v) self.set_result("") else: return "undel_line" def ENTER(self): return self.linechange("down") def MOVE_UP(self): return self.linechange("up") def MOVE_DOWN(self): return self.linechange("down") def PAGE_UP(self): return self.linechange("page up") def PAGE_DOWN(self): return self.linechange("page down") def MOVE_START(self): return self.linechange("home") def MOVE_END(self): return self.linechange("end") def linechange(self, rval): """Updates this field when changing the field focus, i.e. switching lines.""" s = self.get_candidate() if s != "": if self.verify(s): self.set_result(s) self.set_candidate("") else: return None else: # clear old error messages self.inform("") self._edit.set_edit_pos(0) self._edit.reset_del_buffers() self._newline = True return rval def READY_LINE(self): """Prepares this field for editing in the current mode: default clear or default edit.""" if not self._newline: return self._newline = False if self._mode == "clear": self.set_candidate("") else: s = self.get_result() self.set_candidate(s) self._edit.set_edit_pos(len(s)) def klass(self): return "string" class NumberTparOption(StringTparOption): def normalize(self, v): if v in ["INDEF", "Indef", "indef"]: return "INDEF" else: return v def verify(self, v): try: if v != self._previousValue: self.paramInfo.set(v) self.paramInfo.set(self._previousValue) return True except ValueError as e: self.set_candidate("") self.inform(str(e)) return False def klass(self): return "number" class BooleanTparOption(StringTparOption): def __init__(self, *args, **keys): StringTparOption.__init__(self, *args, **keys) self._binder.bind(" ", "space") self._binder.bind("space", self.TOGGLE) self._binder.bind("right", self.TOGGLE) self._binder.bind("left", self.TOGGLE) def TOGGLE(self): if self.get_result() == "yes": self.set_result("no") else: self.set_result("yes") def normalize(self, v): if v in ["n", "N"]: return "no" elif v in ["y", "Y"]: return "yes" else: return v def verify(self, v): v = self.normalize(v) if v in ["yes", "no"]: self.inform("") return True else: self.set_candidate("") self.inform("Not a valid boolean value.") return False def klass(self): return "boolean" class EnumTparOption(StringTparOption): def __init__(self, *args, **keys): StringTparOption.__init__(self, *args, **keys) self._binder.bind(" ", "space") self._binder.bind("space", self.SPACE) self._binder.bind("right", self.SPACE) self._binder.bind("left", self.LEFT) def adjust(self, delta, wrap): choices = self.paramInfo.choice try: v = choices[choices.index(self.get_result()) + delta] except IndexError: v = choices[wrap] self.set_result(v) def SPACE(self): return self.adjust(1, 0) def LEFT(self): return self.adjust(-1, -1) def klass(self): return "enumeration" def verify(self, v): if v not in self.paramInfo.choice: self.inform("What? choose: " + "|".join(self.paramInfo.choice)) self.set_candidate("") return False return True class PsetTparOption(StringTparOption): def klass(self): return "pset" class TparHeader(urwid.Pile): banner = """ I R A F Image Reduction and Analysis Facility """ def __init__(self, package, task=None): top = urwid.Text(("header", self.banner)) s = f"{'PACKAGE':>8}= {package:<10}\n" if task is not None: s += f"{'TASK':>8}= {task:<10}" info = urwid.Text(("body", s)) urwid.Pile.__init__(self, [top, info]) class TparDisplay(Binder): palette = [ ('body', 'default', 'default', 'standout'), ('header', 'default', 'default', ('standout', 'underline')), ('help', 'black', 'light gray'), ('reverse', 'light gray', 'black'), ('important', 'dark blue', 'light gray', ('standout', 'underline')), ('editfc', 'white', 'dark blue', 'bold'), ('editbx', 'light gray', 'dark blue'), ('editcp', 'black', 'light gray', 'standout'), ('bright', 'dark gray', 'light gray', ('bold', 'standout')), ('buttn', 'black', 'dark cyan'), ('buttnf', 'white', 'dark blue', 'bold'), ] def __init__(self, taskName): MODE_KEYS_EMACS = ["esc"] MODE_KEYS_VI = ["esc", "tab", "ctrl u", "ctrl U", "ctrl t", "ctrl T"] TPAR_BINDINGS = { # Page level bindings "quit": self.QUIT, "exit ": self.EXIT, "help": self.HELP, "end": self.MOVE_END, "home": self.MOVE_START, } # Get the Iraftask object if isinstance(taskName, irafpar.IrafParList): # IrafParList acts as an IrafTask for our purposes self.taskObject = taskName else: # taskName must be a string or an IrafTask object self.taskObject = iraf.getTask(taskName) # Now go back and ensure we have the full taskname self.taskName = self.taskObject.getName() self.pkgName = self.taskObject.getPkgname() self.paramList = self.taskObject.getParList(docopy=1) # See if there exist any special versions on disk to load self.__areAnyToLoad = irafpar.haveSpecialVersions( self.taskName, self.pkgName) # irafpar caches them # Ignore the last parameter which is $nargs self.numParams = len(self.paramList) - 1 # Get default parameter values for unlearn self.get_default_param_list() self.make_entries() self.escape = False self._createButtons() self.colon_edit = PyrafEdit("", "", wrap="clip", align="left", inform=self.inform) self.listitems = [urwid.Divider(" ")] + self.entryNo + \ [urwid.Divider(" "), self.colon_edit, self.buttons] self.listbox = urwid.ListBox(self.listitems) self.listbox.set_focus(1) self.footer = urwid.Text("") self.header = TparHeader(self.pkgName, self.taskName) self.view = urwid.Frame(self.listbox, header=self.header, footer=self.footer) self._editor = iraf.envget("editor") BINDINGS = {} BINDINGS.update(TPAR_BINDINGS) if self._editor == "vi": BINDINGS.update(TPAR_BINDINGS_VI) MODE_KEYS = MODE_KEYS_VI else: BINDINGS.update(TPAR_BINDINGS_EMACS) MODE_KEYS = MODE_KEYS_EMACS Binder.__init__(self, BINDINGS, self.inform, MODE_KEYS) def _createButtons(self): """ Set up all the bottom row buttons and their spacings """ isPset = isinstance(self.taskObject, iraftask.IrafPset) self.help_button = urwid.Padding(urwid.Button("Help", self.HELP), align="center", width=8, right=4, left=5) self.cancel_button = urwid.Padding(urwid.Button("Cancel", self.QUIT), align="center", width=10) if not isPset: self.save_as_button = urwid.Padding(urwid.Button( "Save As", self.SAVEAS), align="center", width=11) self.save_button = urwid.Padding(urwid.Button("Save", self.EXIT), align="center", width=8) self.exec_button = urwid.Padding(urwid.Button("Exec", self.go), align="center", width=8) if self.__areAnyToLoad: self.open_button = urwid.Padding(urwid.Button("Open", self.PFOPEN), align="center", width=8) # GUI button layout - weightings if isPset: # show no Open nor Save As buttons self.buttons = urwid.Columns([('weight', 0.20, self.exec_button), ('weight', 0.23, self.save_button), ('weight', 0.23, self.cancel_button), ('weight', 0.20, self.help_button)]) else: if not self.__areAnyToLoad: # show Save As but not Open self.buttons = urwid.Columns([ ('weight', 0.15, self.exec_button), ('weight', 0.15, self.save_button), ('weight', 0.18, self.save_as_button), ('weight', 0.18, self.cancel_button), ('weight', 0.15, self.help_button) ]) else: # show all possible buttons (iterated on this spacing) self.buttons = urwid.Columns([ ('weight', 0.10, self.open_button), ('weight', 0.10, self.exec_button), ('weight', 0.10, self.save_button), ('weight', 0.12, self.save_as_button), ('weight', 0.12, self.cancel_button), ('weight', 0.10, self.help_button) ]) def get_default_param_list(self): # Obtain the default parameter list dlist = self.taskObject.getDefaultParList() if len(dlist) != len(self.paramList): # whoops, lengths don't match raise ValueError("Mismatch between default, current par lists" f" for task {self.taskName} (try unlearn)") pardict = {} for par in dlist: pardict[par.name] = par # Build default list sorted into same order as current list try: dsort = [] for par in self.paramList: dsort.append(pardict[par.name]) except KeyError: raise ValueError("Mismatch between default, current par lists" f" for task {self.taskName} (try unlearn)") self.defaultParamList = dsort # Method to create the parameter entries def make_entries(self): # Loop over the parameters to create the entries self.entryNo = [None] * self.numParams for i in range(self.numParams): self.entryNo[i] = self.tpar_option_factory( self.paramList[i], self.defaultParamList[i]) def main(self): # Create the Screen using curses_display. self.ui = urwid.curses_display.Screen() self.ui.register_palette(self.palette) self.ui.run_wrapper(self.run) # raw_display has alternate_buffer=True self.done() def get_keys(self): keys = [] while not keys: try: keys = self.ui.get_input() except KeyboardInterrupt: keys = ["ctrl c"] return keys def run(self): self.ui.set_mouse_tracking() size = self.ui.get_cols_rows() self.done = False self._newline = True while not self.done: self.view.keypress(size, "ready") canvas = self.view.render(size, focus=1) self.ui.draw_screen(size, canvas) for k in self.get_keys(): if k == ":": self.colon_escape() break elif urwid.is_mouse_event(k): event, button, col, row = k self.view.mouse_event(size, event, button, col, row, focus=True) elif k == 'window resize': size = self.ui.get_cols_rows() self.inform(f"resize {str(size)}") k = self.keypress(size, k) self.view.keypress(size, k) def colon_escape(self): """colon_escape switches the focus to the 'mini-buffer' and accepts and executes a one line colon command.""" w, pos0 = self.listbox.get_focus() try: default_file = w.get_result() except: default_file = "" self.listbox.set_focus(len(self.listitems) - 2) size = self.ui.get_cols_rows() self.colon_edit.set_edit_text("") self.colon_edit.set_edit_pos(0) self.view.keypress(size, ":") done = False while not done: canvas = self.view.render(size, focus=1) self.ui.draw_screen(size, canvas) for k in self.get_keys(): if urwid.is_mouse_event(k) or \ k == "ctrl c" or k == "ctrl g": self.colon_edit.set_edit_text("") return elif k == 'window resize': size = self.ui.get_cols_rows() elif k == 'enter': done = True break k = self.keypress(size, k) self.view.keypress(size, k) cmd = self.colon_edit.get_edit_text() self.listbox.set_focus(pos0) self.colon_edit.set_edit_text("") self.process_colon(cmd) def process_colon(self, cmd): # : [!] [] groups = re.match( "^:(?P[a-z])\\s*" "(?P!?)\\s*" "(?P\\w*)", cmd) if not groups: self.inform("bad command: " + cmd) else: letter = groups.group("cmd") emph = groups.group("emph") == "!" file = groups.group("file") try: f = { "q": self.QUIT, "g": self.go, "r": self.read_pset, "w": self.write_pset, "e": self.edit_pset }[letter] except KeyError: self.inform("unknown command: " + cmd) return try: f(file, emph) except Exception as e: self.inform(f"command '{cmd}' failed with exception '{e}'") def save_as(self): """ Save the parameter settings to a user-specified file. Any changes here must be coordinated with the corresponding epar saveAs function. """ # The user wishes to save to a different name. fname = self.select_file("Save parameter values to which file?", overwriteCheck=True) # Now save the parameters if fname is None: msg = "Parameters NOT saved to a file." okdlg = urwutil.DialogDisplay(msg, 8, 0) okdlg.add_buttons([("OK", 0)]) okdlg.main() return # Tpar apparently does nothing with children (PSETs), so skip the # check or set or save of them # Notify them that pset children will not be saved as part of # their special version pars = [] for par in self.paramList: 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 += " " + p + "\n" msg = msg+"\nthose changes will NOT be explicitly saved to:"+ \ '\n\n"'+fname+'"' # title='PSET Save-As Not Yet Supported okdlg = urwutil.DialogDisplay(msg, 0, 0) okdlg.add_buttons([("OK", 0)]) okdlg.main() # Verify all the entries (without save), keeping track of the invalid # entries which have been reset to their original input values self.badEntriesList = self.check_set_save_entries(False) # If there were invalid entries, prepare the message dialog ansOKCANCEL = True if self.badEntriesList: ansOKCANCEL = self.process_bad_entries(self.badEntriesList, self.taskName) if not ansOKCANCEL: return # should we tell them we are not saving ? # If there were no invalid entries or the user said OK, finally # save to their stated file. Since we have already processed the # bad entries, there should be none returned. mstr = "TASKMETA: task=" + self.taskName + " package=" + self.pkgName if self.check_set_save_entries(doSave=True, filename=fname, comment=mstr): raise Exception("Unexpected bad entries for: " + self.taskName) # Let them know what they just did msg = 'Saved to: "' + fname + '"' okdlg = urwutil.DialogDisplay(msg, 8, 0) okdlg.add_buttons([("OK", 0)]) okdlg.main() # Notify irafpar that there is a new special-purpose file on the scene irafpar.newSpecialParFile(self.taskName, self.pkgName, fname) def pfopen(self): """ Load the parameter settings from a user-specified file. Any changes here must be coordinated with the corresponding epar pfopen function. """ flist = irafpar.getSpecialVersionFiles(self.taskName, self.pkgName) if len(flist) <= 0: msg = "No special-purpose parameter files found for " + self.taskName okdlg = urwutil.DialogDisplay(msg, 8, 0) okdlg.add_buttons([("OK", 0)]) okdlg.main() return fname = None if len(flist) == 1: msg = "One special-purpose parameter file found.\n"+ \ "Load file?\n\n"+flist[0] yesnodlg = urwutil.DialogDisplay(msg, 12, 0) yesnodlg.add_buttons([("OK", 0), ("Cancel", 1)]) rv, junk = yesnodlg.main() if rv == 0: fname = flist[0] # if not, fname is still None else: # >1 file, need a select dialog flist.sort() chcs = [] # ListDialogDisplay takes a 2-column tuple for i in range(len(flist)): chcs.append(str(i)) # need index as tag - it is the return val chcs.append(flist[i]) def menuItemConstr(tag, state): return urwutil.MenuItem(tag) selectdlg = urwutil.ListDialogDisplay("Select from these:", len(flist) + 7, 75, menuItemConstr, tuple(chcs), False) selectdlg.add_buttons([ ("Cancel", 1), ]) rv, ans = selectdlg.main() if rv == 0: fname = flist[int(ans)] # check-point: if fname is not None, we load a file msg = "\n\nPress any key to continue..." if fname is not None: newParList = irafpar.IrafParList(self.taskName, fname) # load it self.set_all_entries_from_par_list(newParList) # set GUI entries msg = "\n\nLoaded:\n\n " + fname + msg # Notify them (also forces a screen redraw, which we need) try: self.ui.clear() # fixes clear when next line calls draw_screen except AttributeError: self.ui._clear() # older urwid vers use different method name self.info(msg, None) def save(self, emph): # Save all the entries and verify them, keeping track of the invalid # entries which have been reset to their original input values if emph: return self.badEntriesList = self.check_set_save_entries(True) # If there were invalid entries, prepare the message dialog ansOKCANCEL = True if (self.badEntriesList): ansOKCANCEL = self.process_bad_entries(self.badEntriesList, self.taskName) return ansOKCANCEL def MOVE_START(self): self.listbox.set_focus(1) return "home" def MOVE_END(self): self.listbox.set_focus(len(self.entryNo)) return "end" # For the following routines, event is either a urwid event *or* # a Pset filename def QUIT(self, event=None, emph=True): # maybe save self.save(emph) def quit_continue(): pass self.done = quit_continue def PFOPEN(self, event=None): """ Open button - load parameters from a user specified file""" self.pfopen() self.done = None # simply continue def SAVEAS(self, event=None): """ SaveAs button - save parameters to a user specified file""" self.save_as() def save_as_continue(): # get back to editing iraffunctions.tparam(self.taskObject) self.done = save_as_continue # self.done = None # will also continue def EXIT(self, event=None): # always save self.QUIT(event, False) # EXECUTE: save the parameter settings and run the task def go(self, event=None, emph=False): """Executes the task.""" self.save(emph) def go_continue(): print(f"\nTask {self.taskName} is running...\n") self.run_task() self.done = go_continue def edit_pset(self, file, emph): """Edits the pset referred to by the specifiefd file or the current field.""" self.save(emph) w, pos0 = self.listbox.get_focus() try: default_file = w.get_result() except: default_file = "" if file == "": iraffunctions.tparam(default_file) else: def edit_pset_continue(): iraffunctions.tparam(file) self.done = edit_pset_continue def read_pset(self, file, emph): """Unlearns the current changes *or* reads in the specified file.""" if file == "": self.unlearn_all_entries() else: def new_pset(): self.__init__(file) self.done = new_pset def write_pset(self, file, overwrite): if os.path.exists(file) and not overwrite: self.inform(f"File '{file}' exists and overwrite (!) not used.") # XXXX write out parameters to file self.inform(f"write pset: {file}") def set_all_entries_from_par_list(self, aParList): """ Set all the parameter entry values in the GUI to the values in the given par list. Note corresponding EditParDialog method. """ for i in range(self.numParams): par = self.paramList[i] if par.type == "pset": continue # skip PSET's for now gui_entry = self.entryNo[i] par.set(aParList.getValue(par.name, native=1, prompt=0)) # gui holds a str, but par.value is native; conversion occurs gui_entry.set_result(par.value) def unlearn_all_entries(self): """ Method to "unlearn" all the parameter entry values in the GUI and set the parameter back to the default value """ for entry in self.entryNo: entry.unlearn_value() # Read, save, and verify the entries def check_set_save_entries(self, doSave, filename=None, comment=None): self.badEntries = [] # Loop over the parameters to obtain the modified information for i in range(self.numParams): par = self.paramList[i] entry = self.entryNo[i] # Cannot change an entry if it is a PSET, just skip if par.type == "pset": continue value = entry.get_result() # Set new values for changed parameters - a bit tricky, # since changes that weren't followed by a return or # tab have not yet been checked. If we eventually # use a widget that can check all changes, we will # only need to check the isChanged flag. if par.isChanged() or value != entry._previousValue: # Verify the value is valid. If it is invalid, # the value will be converted to its original valid value. # Maintain a list of the reset values for user notification. if not entry.verify(value): self.badEntries.append( [entry.paramInfo.name, value, entry._previousValue]) else: self.taskObject.setParam(par.name, value) # Save results to the uparm directory # 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 doSave and ((not isinstance(self.taskObject, irafpar.IrafParList)) or self.taskObject.getFilename()): self.taskObject.saveParList(filename=filename, comment=comment) return self.badEntries # Run the task def run_task(self): # Use the run method of the IrafTask class # Set mode='h' so it does not prompt for parameters (like IRAF epar) # Also turn on parameter saving self.taskObject.run(mode='h', _save=1) def get_results(self): results = {} for i in self.items: results[i.get_name()] = i.get_result() return results def draw_screen(self, size): canvas = self.view.render(size, focus=True) self.ui.draw_screen(size, canvas) def inform(self, s): """output any message to status bar""" self.footer.set_text(s) def info(self, msg, b): self.exit_flag = False size = self.ui.get_cols_rows() exit_button = urwid.Padding(urwid.Button("Exit", self.exit_info), align="center", width=8) frame = urwid.Frame(urwid.Filler(urwid.AttrWrap( urwid.Text(msg), "help"), valign="top"), header=self.header, footer=exit_button) canvas = frame.render(size) self.ui.draw_screen(size, canvas) self.get_keys() # wait for keypress def exit_info(self, ehb): self.exit_flag = True def HELP(self, event=None): if self._editor == "vi": self.info(TPAR_HELP_VI, self.help_button) else: self.info(TPAR_HELP_EMACS, self.help_button) def select_file(self, prompt, overwriteCheck=False): """ Allow user to input a file - handle whether it is expected to be new or existing. Returns file name on success, None on error. """ # Allow the user to select a specific file. Note that urwid's # browser example (browse.py) doesn't work with 0.9.7. while True: try: fname = urwfiledlg.main() except: prompt = "(File chooser error, enter choice manually.)\n" + prompt inputdlg = urwutil.InputDialogDisplay(prompt, 9, 0) inputdlg.add_buttons([("OK", 0), ("Cancel", 1)]) rv, fname = inputdlg.main() if rv > 0: fname = None if fname is None: return None # they canceled fname = fname.strip() if len(fname) == 0: return None # See if the file exists (if we care) if overwriteCheck and os.path.exists(fname): yesnodlg = urwutil.DialogDisplay( "File exists! Overwrite?\n\n " + fname, 9, 0) yesnodlg.add_buttons([("Yes", 0), ("No", 1)]) rv, junk = yesnodlg.main() if rv == 0: return fname # if no, then go thru selection again else: return fname def askokcancel(self, title, msg): self.info(msg, None) return False # Process invalid input values and invoke a query dialog def process_bad_entries(self, badEntriesList, taskname): tpl = "{:>20s} {:>20s} {:>20s}\n" badEntriesString = "\nTask " + taskname.upper() + \ " -- Invalid values have been entered.\n\n" badEntriesString += tpl.format("Parameter", "Bad Value", "Reset Value") for i in range(len(badEntriesList)): badEntriesString += tpl.format(badEntriesList[i][0].strip(), badEntriesList[i][1].strip(), badEntriesList[i][2].strip()) badEntriesString += "\nOK to continue using"\ " the reset\nvalues or cancel to re-enter\nvalues?\n" # Invoke the modal message dialog return (self.askokcancel("Notice", badEntriesString)) # TparOption values for non-string types _tparOptionDict = { "b": BooleanTparOption, "r": NumberTparOption, "d": NumberTparOption, "i": NumberTparOption, "pset": PsetTparOption, "ar": NumberTparOption, "ai": NumberTparOption, } def tpar_option_factory(self, param, defaultParam): """Return TparOption item of appropriate type for the parameter param""" # If there is an enumerated list, regardless of datatype, use # the EnumTparOption if (param.choice is not None): tparOption = EnumTparOption else: # Use String for types not in the dictionary tparOption = self._tparOptionDict.get(param.type, StringTparOption) return tparOption(param, defaultParam, self.inform) def tpar(taskName): if isinstance(urwid, FakeModule): print(f''' The urwid package isn't found on your Python system so tpar can't be used.' (the error given: "{urwid.the_error}")' Please install urwid or use epar instead. ''', file=sys.stderr) return TparDisplay(taskName).main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1646897637.0 pyraf-2.2.1/pyraf/urwfiledlg.py0000644000175000017500000002401714212324745015232 0ustar00olesoles#!/usr/bin/env python3 """ A filechooser for urwid. Author: Rebecca Breu (rebecca@rbreu.de) License: GPL In use with PyRAF until a File-Browser/Chooser comes standard with Urwid. This copy is based on r49 of urwid/contrib/trunk/rbreu_filechooser.py, updated 2006.10.17. Only minor changes were made - mostly to handle use with differing versions of urwid. Many thanks to author Rebecca Breu. """ from urwid import (Text, AttrWrap, WidgetWrap, BoxAdapter, ListBox, AttrWrap, Columns, Edit, Button, GridFlow, CheckBox, Pile, SimpleListWalker) import os from os.path import normpath, abspath, join __all__ = ["FileChooser"] ###################################################################### class SelText(Text): """ A selectable text widget. See urwid.Text. """ def selectable(self): """ Make widget selectable. """ return True def keypress(self, size, key): """ Don't handle any keys. """ return key ###################################################################### class FileChooser(WidgetWrap): """ Creates a dialog (FlowWidget) for choosing a file. It displays the subdirectories and files in the selected directory in two different ListBoxes, and the whole filename of the selected file in an Edit widget. The user can choose from the ListBoxes, or type an arbitrary path in the Edit widget. After pressing enter in the Edit widget, the ListBoxes will be updated accordingly. The FileChooser has some text in English, but one can easiliy create a new widget inheriting from FileChooser and alter the string constants SELECTION_TEXT and SHOW_HIDDEN_TEXT. """ SELECTION_TEXT = "Selection" SHOW_HIDDEN_TEXT = "Show hidden files" selection = None b_pressed = None _blank = Text("") def __init__(self, height, directory=".", file="", attr=(None, None), show_hidden=False): """ height -- height of the directory list and the file list directory, file -- default selection attr -- (inner selectable widgets, selected widgets) show_hidden -- If True, hidden files are shown by default. """ self.directory = abspath(directory) self.file = "" self.attr = attr self.height = height self.show_hidden = show_hidden # Create dummy widgets for directory and file display: self.dir_widget = AttrWrap( BoxAdapter(ListBox([self._blank]), self.height), self.attr[0]) self.file_widget = AttrWrap( BoxAdapter(ListBox([self._blank]), self.height), self.attr[0]) columns = Columns([self.dir_widget, self.file_widget], 1) # Selection widget: self.select_widget = AttrWrap(Edit("", ""), self.attr[0], self.attr[1]) # Buttons and checkbox: button_widgets = [ AttrWrap(Button(button, self._action), attr[0], attr[1]) for button in ["OK", "Cancel"] ] button_grid = GridFlow(button_widgets, 12, 2, 1, 'center') button_cols = Columns([ CheckBox(self.SHOW_HIDDEN_TEXT, self.show_hidden, False, self._toggle_hidden), button_grid ]) self.outer_widget = Pile([ columns, self._blank, Text(self.SELECTION_TEXT), self.select_widget, self._blank, button_cols ]) self.update_widgets() WidgetWrap.__init__(self, self.outer_widget) def _dirfiles(self, directory): """ Get a list of all directories and files in directory. List contains hidden files/dirs only if self.show_hidden is True. """ dirlist = [".", ".."] filelist = [] for entry in os.listdir(directory): path = os.path.join(directory, entry) if self.show_hidden or not entry.startswith("."): if os.path.isdir(path): dirlist.append(entry) elif os.path.isfile(path): filelist.append(entry) else: pass return (dirlist, filelist) def update_widgets(self, update_dir=True, update_file=True, update_select=True): """ Update self.dir_widget, self.file_widget or self.select_widget, corresponding to which of the paramters are set to True. """ if update_dir or update_file: (dirlist, filelist) = self._dirfiles(self.directory) if update_dir: # Directory widget: widget_list = [ AttrWrap(SelText(dir), None, self.attr[1]) for dir in dirlist ] self.dir_widget.box_widget.body = SimpleListWalker(widget_list) if update_file: # File widget: widget_list = [ AttrWrap(SelText(dir), None, self.attr[1]) for dir in filelist ] self.file_widget.box_widget.body = SimpleListWalker(widget_list) if update_select: # Selection widget: selected_file = join(self.directory, self.file) self.select_widget.set_edit_text(selected_file) def _focused_widgets(self): """ Return a list of focused widgets. """ focused = [self.outer_widget] widget = self.outer_widget while hasattr(widget, "get_focus"): widget = widget.get_focus() try: focused.append(widget[0]) except (TypeError, AttributeError): focused.append(widget) return focused def _action(self, button): """ Function called when a button is pressed. Should not be called manually. """ if button.get_label() == "OK": self.selection = self.select_widget.get_edit_text() self.b_pressed = button.get_label() def _toggle_hidden(self, checkbox, new_state): """ Function called when the \"Show hidden files\" checkbox ist toggled. Should not be called manually. """ self.show_hidden = new_state self.update_widgets(True, True) def keypress(self, size, key): """ key selects a path or file, other keys will be passed to the Pile widget. """ if key == "enter": focused = self._focused_widgets() if focused[-2] == self.dir_widget: # User has selected a directory from the list: new_dir = focused[-1].w.get_text()[0] self.directory = normpath(join(self.directory, new_dir)) self.file = "" self.update_widgets() return elif focused[-2] == self.file_widget: # User has selected a file from the list: self.file = focused[-1].w.get_text()[0] self.update_widgets(False, False, True) return elif focused[-1] == self.select_widget: # User has pressed enter in the "Selection Widget": path = self.select_widget.get_edit_text() (self.directory, self.file) = os.path.split(path) self.update_widgets(True, True, False) return return self.outer_widget.keypress(size, key) def mouse_event(self, size, event, button, col, row, focus): """ First mouse button selects a path or file, other keys will be passed to the Pile widget. """ handled = self.outer_widget.mouse_event(size, event, button, col, row, focus) if event == "mouse press" and button == 1: focused = self._focused_widgets() if focused[-2] == self.dir_widget: # User has selected a directory from the list: new_dir = focused[-1].w.get_text()[0] self.directory = normpath(join(self.directory, new_dir)) self.file = "" self.update_widgets() return elif focused[-2] == self.file_widget: # User has selected a file from the list: self.file = focused[-1].w.get_text()[0] focus = (self.dir_widget.get_focus()[1], self.file_widget.get_focus()[1]) self.update_widgets(False, False, True) return handled = True return handled ###################################################################### # End of module part ###################################################################### import urwid def main(): global selection global ui ui = urwid.curses_display.Screen() ui.register_palette([ ('menu', 'black', 'dark cyan', 'standout'), ('menuf', 'black', 'light gray'), ('bg', 'light gray', 'dark blue'), ]) return ui.run_wrapper(run) def run(): global selection global ui ui.set_mouse_tracking() dim = ui.get_cols_rows() widget = urwid.Filler(FileChooser(height=10, attr=("menu", "menuf"))) widget = urwid.AttrWrap(widget, "bg") keys = True while True: if keys: ui.draw_screen(dim, widget.render(dim, True)) keys = ui.get_input() if widget.body.b_pressed == "OK": selection = "You selected: " + widget.body.selection return widget.body.selection if widget.body.b_pressed == "Cancel": selection = "No file selected." return if "window resize" in keys: dim = ui.get_cols_rows() for k in keys: if urwid.is_mouse_event(k): event, button, col, row = k widget.mouse_event(dim, event, button, col, row, focus=True) else: widget.keypress(dim, k) if __name__ == "__main__": import urwid import urwid.curses_display print(main()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1646897637.0 pyraf-2.2.1/pyraf/urwutil.py0000755000175000017500000002202114212324745014575 0ustar00olesoles# # Urwid example similar to dialog(1) program # Copyright (C) 2004-2007 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: http://excess.org/urwid/ """ July 2008 - taken from the urwid example script "dialog.py", for the use of the DialogDisplay and the InputDialogDisplay classes. Unsure why this functionality is not delivered with a standard urwid installation, so we will include this file until it comes with urwid. This is slightly modified. """ import sys import urwid import urwid.raw_display class DialogExit(Exception): pass class DialogDisplay: palette = [ ('body', 'black', 'light gray', 'standout'), ('border', 'black', 'dark blue'), ('shadow', 'white', 'black'), ('selectable', 'black', 'dark cyan'), ('focus', 'white', 'dark blue', 'bold'), ('focustext', 'light gray', 'dark blue'), ] def __init__(self, text, height, width, body=None): width = int(width) if width <= 0: width = ('relative', 80) height = int(height) if height <= 0: height = ('relative', 80) self.body = body if body is None: # fill space with nothing body = urwid.Filler(urwid.Divider(), 'top') self.frame = urwid.Frame(body, focus_part='footer') if text is not None: self.frame.header = urwid.Pile([urwid.Text(text), urwid.Divider()]) w = self.frame # pad area around listbox w = urwid.Padding(w, ('fixed left', 2), ('fixed right', 2)) w = urwid.Filler(w, ('fixed top', 1), ('fixed bottom', 1)) w = urwid.AttrWrap(w, 'body') # "shadow" effect w = urwid.Columns([ w, ('fixed', 2, urwid.AttrWrap(urwid.Filler(urwid.Text(('border', ' ')), "top"), 'shadow')) ]) w = urwid.Frame(w, footer=urwid.AttrWrap(urwid.Text(('border', ' ')), 'shadow')) # outermost border area w = urwid.Padding(w, 'center', width) w = urwid.Filler(w, 'middle', height) w = urwid.AttrWrap(w, 'border') self.view = w def add_buttons(self, buttons): l = [] for name, exitcode in buttons: b = urwid.Button(name, self.button_press) b.exitcode = exitcode b = urwid.AttrWrap(b, 'selectable', 'focus') l.append(b) self.buttons = urwid.GridFlow(l, 10, 3, 1, 'center') self.frame.footer = urwid.Pile([urwid.Divider(), self.buttons], focus_item=1) def button_press(self, button): raise DialogExit(button.exitcode) def main(self): self.ui = urwid.raw_display.Screen() self.ui.register_palette(self.palette) return self.ui.run_wrapper(self.run) def run(self): self.ui.set_mouse_tracking() size = self.ui.get_cols_rows() try: while True: canvas = self.view.render(size, focus=True) self.ui.draw_screen(size, canvas) keys = None while not keys: keys = self.ui.get_input() for k in keys: if urwid.is_mouse_event(k): event, button, col, row = k self.view.mouse_event(size, event, button, col, row, focus=True) if k == 'window resize': size = self.ui.get_cols_rows() k = self.view.keypress(size, k) if k: self.unhandled_key(size, k) except DialogExit as e: return self.on_exit(e.args[0]) def on_exit(self, exitcode): return exitcode, "" def unhandled_key(self, size, key): pass class InputDialogDisplay(DialogDisplay): def __init__(self, text, height, width): self.edit = urwid.Edit() body = urwid.ListBox([self.edit]) body = urwid.AttrWrap(body, 'selectable', 'focustext') DialogDisplay.__init__(self, text, height, width, body) self.frame.set_focus('body') def unhandled_key(self, size, k): if k in ('up', 'page up'): self.frame.set_focus('body') if k in ('down', 'page down'): self.frame.set_focus('footer') if k == 'enter' or k == 'ctrl m': # STScI change ! add ctrl-m for OSX # pass enter to the "ok" button self.frame.set_focus('footer') self.view.keypress(size, 'enter') def on_exit(self, exitcode): return exitcode, self.edit.get_edit_text() # # ListDialogDisplay example: # # choices = ['a','b','c','d'] # import urwutil # import urwid # def mmm(tag, state): return urwutil.MenuItem(tag) # chcs = [] # for itm in choices: # chcs.append(itm) # chcs.append('') # empty state # dlg=urwutil.ListDialogDisplay("select: ", len(choices)+7, # 75, mmm, tuple(chcs), False) # dlg.add_buttons([ ("Cancel",1), ]) # rv, item = dlg.main() # use item if rv == 0 # class ListDialogDisplay(DialogDisplay): def __init__(self, text, height, width, constr, items, has_default): j = [] if has_default: k, tail = 3, () else: k, tail = 2, ("no",) while items: j.append(items[:k] + tail) items = items[k:] l = [] self.items = [] for tag, item, default in j: w = constr(tag, default == "on") self.items.append(w) w = urwid.Columns([('fixed', 12, w), urwid.Text(item)], 2) w = urwid.AttrWrap(w, 'selectable', 'focus') l.append(w) lb = urwid.ListBox(l) lb = urwid.AttrWrap(lb, "selectable") DialogDisplay.__init__(self, text, height, width, lb) self.frame.set_focus('body') def unhandled_key(self, size, k): if k in ('up', 'page up'): self.frame.set_focus('body') if k in ('down', 'page down'): self.frame.set_focus('footer') if k == 'enter': # pass enter to the "ok" button self.frame.set_focus('footer') self.buttons.set_focus(0) self.view.keypress(size, k) def on_exit(self, exitcode): """Print the tag of the item selected.""" if exitcode != 0: return exitcode, "" s = "" for i in self.items: if i.get_state(): s = i.get_label() break return exitcode, s class MenuItem(urwid.Text): """A custom widget for the ListDialog""" def __init__(self, label): urwid.Text.__init__(self, label) self.state = False def selectable(self): return True # The change below (in the if) was made by STScI ! def keypress(self, size, key): if key == "enter" or \ key == "ctrl m" or key == " ": # is ctrl-m on OSX; also allow space self.state = True raise DialogExit(0) return key def mouse_event(self, size, event, button, col, row, focus): if event == 'mouse release': self.state = True raise DialogExit(0) return False def get_state(self): return self.state def get_label(self): text, attr = self.get_text() return text def show_usage(): """ Display a helpful usage message. """ sys.stdout.write(__doc__ + "\n\t" + sys.argv[0] + " text height width\n" + """ height and width may be set to 0 to auto-size. list-height and menu-height are currently ignored. status may be either on or off. """) if __name__ == "__main__": if len(sys.argv) < 4: show_usage() sys.exit(1) # Create a DialogDisplay instance d = InputDialogDisplay(sys.argv[1], sys.argv[2], sys.argv[3]) # for simple yes/no dialog: d = DialogDisplay(text, height, width) d.add_buttons([("OK", 0), ("Cancel", 1)]) # Run it exitcode, exitstring = d.main() # Exit if exitstring: sys.stderr.write(exitstring + "\n") sys.exit(exitcode) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf/version.py0000644000175000017500000000021614223000070014526 0ustar00olesoles# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control version = '2.2.1' version_tuple = (2, 2, 1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1647341865.0 pyraf-2.2.1/pyraf/wutil.py0000644000175000017500000005364614214070451014236 0ustar00olesoles"""Contains python routines to do special Window manipulations not possible in tkinter. These are python stubs that are overloaded by a c version implementations. If the c versions do not exist, then these routines will do nothing """ import struct import sys import os import fcntl # empty placeholder versions for X def getFocalWindowID(): return None def drawCursor(WindowID, x, y, w, h): pass def moveCursorTo(WindowID, rx, ry, x, y): pass def setFocusTo(WindowID): pass def setBackingStore(WindowID): pass def getPointerPosition(WindowID): pass def getWindowAttributes(WindowID): pass def getParentID(WindowID): pass def getDeepestVisual(): return 24 def initGraphics(): pass def closeGraphics(): pass # On OSX, a terminal with no display causes us to fail pretty abruptly: # "INIT_Processeses(), could not establish the default connection to the WindowServer.Abort". # Give the user (Mac or other) a way to still run remotely with no display. from .tools import capable _skipDisplay = not capable.OF_GRAPHICS # Are we on MacOS X ? Windows ? WUTIL_ON_MAC = sys.platform == 'darwin' WUTIL_ON_WIN = sys.platform.startswith('win') # WUTIL_USING_X: default to using X on most platforms, tho surely not on windows WUTIL_USING_X = not WUTIL_ON_WIN # More on this for OSX: for now we support both versions (X or Aqua) on OSX # Allow environment variable so any user can force their preference. if WUTIL_ON_MAC and not _skipDisplay: # default to aqua as it is the 98% case now (post v2.1.9), X11 users will need the env. var WUTIL_USING_X = 'PYRAF_WUTIL_USING_X' in os.environ # Experimental new (2012) mode some have requested (OSX mostly) where all # graphics windows drawn are popped to the foreground and left there with # the focus (focus not placed back onto terminal). Except the splash win. GRAPHICS_ALWAYS_ON_TOP = 'PYRAF_GRAPHICS_ALWAYS_ON_TOP' in os.environ # attempt to override with xutil or aqua versions _has_aqutil = 0 _has_xutil = 0 try: if WUTIL_USING_X and not _skipDisplay: # set an env var before importing xutil (see PyRAF FAQ on this) os.environ['XLIB_SKIP_ARGB_VISUALS'] = '1' from . import xutil # initGraphics = initXGraphics xutil.initXGraphics() # call here for lack of a better place for n # Check to make sure a valid XWindow ID was initialized # Attach closeGraphics to XWindow methods # ONLY if an XWindow was successfully initialized. # WJH (10June2004) if xutil.getFocalWindowID() == -1: raise OSError() # Successful intialization. Reset dummy methods with # those from 'xutil' now. from pyraf.xutil import * _has_xutil = 1 # Flag to mark successful initialization of XWindow closeGraphics = closeXGraphics else: # Start with a basic empty non-X implementation (e.g. Cygwin?, OSX, ?) def getWindowIdZero(): return 0 getFocalWindowID = getWindowIdZero # If on OSX w/out X11, use aqutil if WUTIL_ON_MAC and not _skipDisplay: # as opposed to the PC (future?) try: from . import aqutil # override the few Mac-specific functions needed from .aqutil import getFocalWindowID, setFocusTo, getParentID from .aqutil import moveCursorTo, getPointerPosition _has_aqutil = 1 except ImportError: _has_aqutil = 0 print("Could not import aqutil") except ImportError: _has_xutil = 0 # Unsuccessful init of XWindow except OSError: _has_xutil = 0 # Unsuccessful init of XWindow # Clean up the namespace a bit... try: del xutil except NameError: pass # may not have imported it import termios magicConstant = termios.TIOCGWINSZ def getScreenDepth(): return getDeepestVisual() # maintain a dictionary of top level IDs to avoid repeated effort here topIDmap = {} def getTopID(WindowID): """Find top level windows ID, parent of given window. If window is already top (or not implemented), it returns its own ID. If the input Id represents the root window then it will just return itself""" wid = WindowID if wid <= 0: return wid # a "top ID" makes less sense if we are not using X if not WUTIL_USING_X: if _has_aqutil: return aqutil.getTopIdFor(wid) else: return wid # everything is its own top if wid in topIDmap: return topIDmap[wid] try: oid = wid while True: pid = getParentID(wid) if (not pid) or (pid == wid): topIDmap[oid] = wid return wid else: wid = pid except OSError: return None def forceFocusToNewWindow(): """ This is used to make sure that a window which just popped up is actually in the front, where focus would be. With X, any new window comes to the front anyway, so this is a no-op. Currently this is only necessary under Aqua. """ if _has_aqutil: aqutil.focusOnGui() def isViewable(WindowID): if not WUTIL_USING_X: return 1 # native OSX code still under dev.; make everything viewable attrdict = getWindowAttributes(WindowID) if attrdict: return attrdict['viewable'] else: return 1 def getTermWindowSize(): """return a tuple containing the y,x (rows,cols) size of the terminal window in characters""" if magicConstant is None: raise Exception("platform isn't supported: " + sys.platform) # define string to serve as memory area to receive copy of structure # created by IOCTL call tstruct = ' ' * 20 # that should be more than enough memory try: rstruct = fcntl.ioctl(sys.stdout.fileno(), magicConstant, tstruct) ysize, xsize = struct.unpack('hh', rstruct[0:4]) # handle bug in konsole (and maybe other bad cases) if ysize <= 0: ysize = 24 if xsize <= 0: xsize = 80 return ysize, xsize except OSError: return (24, 80) # assume generic size class FocusEntity: """Represents an interface to peform focus manipulations on a variety of window objects. This allows the windows to be handled by code that does not need to know the specifics of how to set focus to, restore focus to, warp the cursor to, etc. Since nothing is implemented, it isn't necessary to inherit it, but inheriting it does allow type checks to see if an object is a subclass of FocusEntity. """ def saveCursorPos(self): """When this method is called, the object should know how to save the current position of the cursor in the window. If the cursor is not in the window or the window does not currently have focus, it should do nothing.""" # raise exceptions to ensure implemenation of required methods raise NotImplementedError("class FocusEntity cannot be used directly") def forceFocus(self, cursorToo=True): """When called, the object should force focus to the window it represents and warp the cursor to it using the last saved cursor position.""" raise NotImplementedError("class FocusEntity cannot be used directly") def getWindowID(self): """return a window ID that can be used to find the top window of the window heirarchy.""" raise NotImplementedError("class FocusEntity cannot be used directly") class TerminalFocusEntity(FocusEntity): """Implementation of FocusEntity interface for the originating terminal window""" def __init__(self): """IMPORTANT: This class must be instantiated while focus is in the terminal window""" self.lastScreenX = None self.lastScreenY = None try: self.windowID = getFocalWindowID() if self.windowID == -1: self.windowID = None if _has_aqutil: scrnPosDict = aqutil.getPointerGlobalPosition() self.lastScreenX = scrnPosDict['x'] self.lastScreenY = scrnPosDict['y'] except OSError: self.windowID = None self.lastX = 30 self.lastY = 30 def getWindowID(self): return self.windowID def forceFocus(self, cursorToo=True): if WUTIL_ON_MAC and WUTIL_USING_X: return # X ver. under dev. on OSX... (was broken anyway) if not (self.windowID and isViewable(self.windowID)): # no window or not viewable return if self.windowID == getFocalWindowID(): # focus is already here return if _has_aqutil: if self.lastScreenX is not None and cursorToo: moveCursorTo(self.windowID, self.lastScreenX, self.lastScreenY, 0, 0) else: # WUTIL_USING_X if self.lastX is not None and cursorToo: moveCursorTo(self.windowID, 0, 0, self.lastX, self.lastY) if not GRAPHICS_ALWAYS_ON_TOP: setFocusTo(self.windowID) def saveCursorPos(self): if (not self.windowID) or (self.windowID != getFocalWindowID()): return if _has_aqutil: scrnPosDict = aqutil.getPointerGlobalPosition() self.lastScreenX = scrnPosDict['x'] self.lastScreenY = scrnPosDict['y'] return if not WUTIL_USING_X: return # some of the following xutil methods are undefined # This also won't work on a Mac if running from the Terminal app # but it WILL work on a Mac from an X11 xterm window if WUTIL_USING_X and WUTIL_ON_MAC and self.windowID < 2: return posdict = getPointerPosition(self.windowID) if posdict: x = posdict['win_x'] y = posdict['win_y'] else: return windict = getWindowAttributes(self.windowID) if windict and windict['width'] > 0: maxX = windict['width'] maxY = windict['height'] else: return # do nothing if position out of window if x < 0 or y < 0 or x >= maxX or y >= maxY: return self.lastX = x self.lastY = y # some extra utility methods def updateWindowID(self, id=None): """Update terminal window ID (to current window if id is not given)""" if id is None: id = getFocalWindowID() self.windowID = id def getWindowSize(self): """return a tuple containing the x,y size of the terminal window in characters""" # define string to serve as memory area to receive copy of structure # created by IOCTL call tstruct = ' ' * 20 # that should be more than enough memory # xxx exception handling needed (but what exception to catch?) rstruct = fcntl.ioctl(sys.stdout.fileno(), magicConstant, tstruct) xsize, ysize = struct.unpack('hh', rstruct[0:4]) return xsize, ysize class FocusController: """A mediator that allows different components to give responsibility to this class for deciding how to manipulate focus. It is this class that knows what elements are available and where focus should be returned to when asked to restore the previous focus and cursor position. The details of doing it for different windows are encapsulated in descendants of the FocusEntity objects that it contains. Since this is properly a singleton, it is created by the wutil module itself and accessed as an object of wutil""" def __init__(self, termwindow): self.focusEntities = {'terminal': termwindow} self.focusStack = [termwindow] self.hasGraphics = termwindow.getWindowID() is not None def addFocusEntity(self, name, focusEntity): if name == 'terminal': return # ignore any attempts to change terminal entity if name in self.focusEntities: return # ignore for now, not sure what proper behavior is self.focusEntities[name] = focusEntity def removeFocusEntity(self, focusEntityName): if focusEntityName in self.focusEntities: entity = self.focusEntities[focusEntityName] del self.focusEntities[focusEntityName] try: while True: self.focusStack.remove(entity) except ValueError: pass def restoreLast(self): if not self.hasGraphics: return if len(self.focusStack) > 1: # update current position if we're in the correct window current = self.focusStack.pop() if current.getWindowID() == getFocalWindowID(): current.saveCursorPos() if self.focusInFamily(): self.focusStack[-1].forceFocus() def setCurrent(self, force=0): """This is to be used in cases where focus has been lost to a window not part of this scheme (dialog boxes for example) and it is desired to return focus to the entity currently considered active.""" if self.hasGraphics and (force or self.focusInFamily()): self.focusStack[-1].forceFocus() def resetFocusHistory(self): # self.focusStack = [self.focusEntities['terminal']] last = self.focusStack[-1] self.focusStack = self.focusStack[:1] if last != self.focusStack[-1]: self.setCurrent() def getCurrentFocusEntity(self): """Return the focus entity that currently has focus. Return None if focus is not in the focus family""" if not self.hasGraphics: return None, None currentFocusWinID = getFocalWindowID() currentTopID = getTopID(currentFocusWinID) for name, focusEntity in self.focusEntities.items(): if getTopID(focusEntity.getWindowID()) == currentTopID: return name, focusEntity else: return None, None def saveCursorPos(self): if self.hasGraphics: name, focusEntity = self.getCurrentFocusEntity() if focusEntity: focusEntity.saveCursorPos() def setFocusTo(self, focusTarget, always=0): """focusTarget can be a string or a FocusEntity. It is possible to give a FocusEntity that is not in focusEntities (so it isn't considered part of the focus family, but is part of the restore chain.) If always is true, target is added to stack even if it is already the focus (useful for pairs of setFocusTo/restoreLast calls.) """ if (focusTarget is None) or (not self.hasGraphics): return if not WUTIL_USING_X: if hasattr(focusTarget, 'gwidget'): # gwidget is a Canvas focusTarget.gwidget.focus_set() current = self.focusStack[-1] if isinstance(focusTarget, str): next = self.focusEntities[focusTarget] else: next = focusTarget # only append if focus stack last entry different from new if next != self.focusStack[-1] or always: self.focusStack.append(next) if self.focusInFamily(): current.saveCursorPos() next.forceFocus() def getFocusEntity(self, FEName): """See if named Focus Entity is currently registered. Return it if it exists, None otherwise""" return self.focusEntities.get(FEName) def focusInFamily(self): """Determine if current focus is within the pyraf family (as defined by self.focusEntities)""" if not self.hasGraphics: return 0 currentFocusWinID = getFocalWindowID() currentTopID = getTopID(currentFocusWinID) for focusEntity in self.focusEntities.values(): fwid = focusEntity.getWindowID() if fwid: if getTopID(fwid) == currentTopID: return 1 return 0 # not in family def getCurrentMark(self): """Returns mark that can be used to restore focus to current setting""" return len(self.focusStack) def restoreToMark(self, mark): """Restore focus to value at mark""" last = self.focusStack[-1] self.focusStack = self.focusStack[:mark] if last != self.focusStack[-1]: self.setCurrent() terminal = TerminalFocusEntity() focusController = FocusController(terminal) # debug helper def dumpspecs(outstream=None, skip_volatiles=False): """ Dump various flags, settings, values to the terminal. This is not to be used internal to this module - it must wait until the module is fully imported for all the values to be finalized. If outstream is not given, this will simply dump to sys.stdout. """ pyrver = 'unknown' try: from pyraf import __version__ as pyrver except ImportError: pass out = "python exec = " + str(sys.executable) if skip_volatiles: out += "\npython ver = " + '.'.join( [str(v) for v in sys.version_info[0:2]]) else: out += "\npython ver = " + '.'.join( [str(v) for v in sys.version_info[0:3]]) out += "\nplatform = " + str(sys.platform) if not skip_volatiles: out += "\nPyRAF ver = " + pyrver out += "\nc.OF_GRAPHICS = " + str(capable.OF_GRAPHICS) dco = 'not yet known' if skip_volatiles: out += "\n/dev/console owner = " else: dco = capable.get_dc_owner(False, True) out += "\n/dev/console owner = " + str(dco) if not capable.OF_GRAPHICS: out += "\ntkinter use unattempted." else: import tkinter out += "\nTclVersion = " + str(tkinter.TclVersion) out += "\nTkVersion = " + str(tkinter.TkVersion) out += "\nWUTIL_ON_MAC = " + str(WUTIL_ON_MAC) out += "\nWUTIL_ON_WIN = " + str(WUTIL_ON_WIN) out += "\nWUTIL_USING_X = " + str(WUTIL_USING_X) out += "\nis_darwin_and_x = " + str(capable.is_darwin_and_x()) if WUTIL_ON_MAC: out += "\nwhich_darwin_linkage = " + str( capable.which_darwin_linkage()) out += "\nwhich_darwin_linkage2 = " + str( capable.which_darwin_linkage(force_otool_check=True)) else: out += "\nwhich_darwin_linkage = (not darwin)" out += "\nskip display = " + str(_skipDisplay) out += "\nhas graphics = " + str(hasGraphics) out += "\nimported aqutil = " + str(bool(_has_aqutil)) out += "\nimported xutil = " + str(bool(_has_xutil)) # Case of WUTIL_USING_X and not _has_xutil means either they don't have # xutil library installed, or they do but they can't draw to screen if WUTIL_USING_X and capable.OF_GRAPHICS and \ not _skipDisplay and not bool(_has_xutil): # quick debug help here for case where xutil didn't build out += '\n\tWARNING! PyRAF may be missing the "xutil" library. See PyRAF FAQ 1.9' if 'PYRAFGRAPHICS' in os.environ: val = os.environ['PYRAFGRAPHICS'] out += "\nPYRAFGRAPHICS = " + val if val == "matplotlib": mpl_ok = False try: import matplotlib as mpl mpl_ok = True except ImportError: out += "\nCannot import matplotlib" if mpl_ok: if hasattr(mpl, 'tk_window_focus'): out += "\nmpl.tk_window_focus = " + str( mpl.tk_window_focus()) else: out += "\nmpl.tk_window_focus = function not supported" mpldir = os.path.split(mpl.__file__)[0] import glob flist = glob.glob(mpldir + os.path.sep + 'backends' + os.path.sep + '*.so') flist = [os.path.split(f)[1] for f in flist] out += "\nmpl backends = " + str(flist) tkaggbknd = mpldir + '/backends/_tkagg.so' if os.path.exists(tkaggbknd): out += "\ntry: /usr/bin/otool -L " + tkaggbknd else: out += "\nPYRAFGRAPHICS not set" if outstream: outstream.write(out + '\n') else: print(out) # Finally, do we have access to a graphics display? hasGraphics = None if _skipDisplay: # A common _skipDisplay case is pyraf being imported in a script, # in which case we keep quiet about the lack of graphics. # But DO warn for interactive sessions where they didn't use '-s' if sys.argv[0].find('pyraf') >= 0 and \ '-s' not in sys.argv and '--silent' not in sys.argv: # Warn, but be specific about why if 'PYRAF_NO_DISPLAY' in os.environ: print("No graphics/display intended for this session.") else: print("No graphics/display possible for this session.") else: if _has_xutil or _has_aqutil: hasGraphics = focusController.hasGraphics elif WUTIL_ON_MAC: # on a Mac but loaded no graphcs libs (aqutil/xutil) # Handle case where we are on the Mac with no X and no PyObjc. We can # still run, albeit without automatic mouse moving and focus jumping. hasGraphics = focusController.hasGraphics if hasGraphics: if capable.which_darwin_linkage() == 'aqua': print( "\nLimited graphics available on OSX (aqutil not loaded)\n" ) else: print( "\nLimited graphics available on OSX (xutil not loaded)\n" ) elif WUTIL_ON_WIN: hasGraphics = 1 # try this, tho VERY limited (epar only I guess) print("\nLimited graphics available on win32 platform\n") if not hasGraphics: print("") print("No graphics display available for this session.") print("Graphics tasks that attempt to plot to an interactive " "screen will fail.") print('For help, search "PyRAF FAQ 5.13"') print("") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/pyraf/xutil.c0000644000175000017500000003100414203121554014010 0ustar00olesoles#include #include #include #include #include #include #include #include #include /* Windows and cursor manipulations not provided by Tkinter or any other ** standard python library. This file handles Python 3 as well. ** see also: http://docs.python.org/py3k/extending ** see also: http://docs.python.org/howto/cporting.html ** see also: http://python3porting.com/cextensions.html */ /* the following macro is used to trap x window exceptions and trigger ** a python exception in turn */ jmp_buf ErrorEnv; int xstatus; int (* oldErrorHandler)(Display *, XErrorEvent *); int (* oldIOErrorHandler)(Display *); char XErrorMsg[80]; char ErrorPrefix[] = "XWindows Error!\n"; char ErrorMsg[120]; char IOError[] = "XWindows IO exception."; static Display *d; static int screen_num; static GC gc; static XWindowAttributes wa; static int last_win = -1; static Colormap cmap; static XColor colorfg, colorbg, color; #define TrapXlibErrors \ oldIOErrorHandler = XSetIOErrorHandler(&MyXlibIOErrorHandler); \ oldErrorHandler = XSetErrorHandler(&MyXlibErrorHandler); \ xstatus = setjmp(ErrorEnv); \ if ( xstatus != 0) { \ XSetIOErrorHandler(oldIOErrorHandler); \ XSetErrorHandler(oldErrorHandler); \ strncat(ErrorMsg,ErrorPrefix, 20); \ strncat(ErrorMsg,XErrorMsg, 80); \ PyErr_SetString(PyExc_EnvironmentError, ErrorMsg); \ return NULL; \ } #define RestoreOldXlibErrorHandlers \ XSetIOErrorHandler(oldIOErrorHandler); \ XSetErrorHandler(oldErrorHandler); int MyXlibErrorHandler(Display *d, XErrorEvent *myerror) { XGetErrorText(d, myerror->error_code, XErrorMsg, 80); longjmp(ErrorEnv, 1); /* Pointless, but it shuts up some compiler warning messages */ return 0; } int MyXlibIOErrorHandler(Display *d) { /* just put a constant string in the error message */ strncat(XErrorMsg,IOError,79); longjmp(ErrorEnv, 1); /* Pointless, but it shuts up some compiler warning messages */ return 0; } // rx,ry are unused by the X version of this; x,y are w.r.t. the given window void moveCursorTo(int win, int rx, int ry, int x, int y) { Window w; /* Display *XOpenDisplay(char *); */ if (d == NULL) { printf("could not open XWindow display\n"); return; } w = (Window) win; XGrabPointer(d,w,True,ButtonPressMask | EnterWindowMask,GrabModeSync, GrabModeSync,None,None,CurrentTime); XWarpPointer(d,None,w,0,0,0,0,x,y); XUngrabPointer(d,CurrentTime); XFlush(d); } PyObject *wrap_moveCursorTo(PyObject *self, PyObject *args) { int w, rx, ry, x, y; if (!PyArg_ParseTuple(args, "iiiii", &w, &rx, &ry, &x, &y)) return NULL; TrapXlibErrors /* macro code to handle xlib exceptions */ moveCursorTo(w, rx, ry, x, y); RestoreOldXlibErrorHandlers /* macro */ Py_INCREF(Py_None); return Py_None; } int getFocalWindowID(void) { Window w; int revert; /* Display *XOpenDisplay(char *); */ if (d == NULL) { printf("could not open XWindow display\n"); return -1; } XGetInputFocus(d,&w,&revert); XFlush(d); return (int) w; } int getDeepestVisual(void) { XVisualInfo *visualList; int i, visualsMatched, maxDepth; maxDepth = 1; if (d == NULL) { printf("could not open XWindow display\n"); return -1; } visualList = XGetVisualInfo (d, VisualNoMask, NULL, &visualsMatched); for (i=0;i maxDepth) { maxDepth = visualList[i].depth; } } XFree(visualList); XFlush(d); return maxDepth; } PyObject *wrap_getDeepestVisual(PyObject *self, PyObject *args) { int depth; TrapXlibErrors /* macro code to handle xlib exceptions */ depth = getDeepestVisual(); RestoreOldXlibErrorHandlers /* macro */ return Py_BuildValue("i",depth); } PyObject *wrap_getFocalWindowID(PyObject *self, PyObject *args) { int result; TrapXlibErrors /* macro code to handle xlib exceptions */ result = getFocalWindowID(); RestoreOldXlibErrorHandlers /* macro */ return Py_BuildValue("i",result); } void setFocusTo(int win) { Window w; /* Display *XOpenDisplay(char *); */ w = (Window) win; if (d == NULL) { printf("could not open XWindow display\n"); return; } XSetInputFocus(d,w,0,CurrentTime); XFlush(d); } PyObject *wrap_setFocusTo(PyObject *self, PyObject *args) { int win; if (!PyArg_ParseTuple(args,"i", &win)) return NULL; TrapXlibErrors /* macro code to handle xlib exceptions */ setFocusTo(win); RestoreOldXlibErrorHandlers /* macro */ Py_INCREF(Py_None); return Py_None; } void setBackingStore(int win) { Window w; XWindowAttributes wa; XSetWindowAttributes NewWinAttributes; Status XGetWindowAttributes(Display *, Window, XWindowAttributes *); int XChangeWindowAttributes(Display *, Window, unsigned long, XSetWindowAttributes *); w = (Window) win; if (d == NULL) { printf("could not open XWindow display\n"); return; } XGetWindowAttributes(d, w, &wa); if (XDoesBackingStore(wa.screen) != NotUseful) { NewWinAttributes.backing_store = Always; XChangeWindowAttributes(d, w, CWBackingStore, &NewWinAttributes); } XFlush(d); } PyObject *wrap_setBackingStore(PyObject *self, PyObject *args) { int win; if (!PyArg_ParseTuple(args,"i", &win)) return NULL; TrapXlibErrors /* macro code to handle xlib exceptions */ setBackingStore(win); RestoreOldXlibErrorHandlers /* macro */ Py_INCREF(Py_None); return Py_None; } void getWindowAttributes(int win, XWindowAttributes *winAttr, char **visual) { Window w; XVisualInfo visual_info; static char *visual_class[] = { "StaticGray", "GrayScale", "StaticColor", "PseudoColor", "TrueColor", "DirectColor" }; int i; int screen_num; Status XGetWindowAttributes(Display *, Window, XWindowAttributes *); i = 5; w = (Window) win; if (d == NULL) { printf("could not open XWindow display\n"); return; } XGetWindowAttributes(d, w, winAttr); screen_num = DefaultScreen(d); while (!XMatchVisualInfo(d, screen_num, DefaultDepth(d, screen_num), i--, &visual_info)); *visual = visual_class[++i]; } PyObject *wrap_getWindowAttributes(PyObject *self, PyObject *args) { int win, viewable; XWindowAttributes wa; char *visual = NULL; if (!PyArg_ParseTuple(args,"i", &win)) return NULL; TrapXlibErrors /* macro code to handle xlib exceptions */ getWindowAttributes(win, &wa, &visual); if (wa.map_state == IsViewable) { viewable = 1; } else { viewable = 0; } RestoreOldXlibErrorHandlers /* macro */ return Py_BuildValue("{s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:s}", "x", wa.x, "y", wa.y, "rootID", (int) wa.root, "width", wa.width, "height", wa.height, "borderWidth", wa.border_width, "viewable", viewable, "depth", wa.depth, "visualClass", visual); } PyObject *wrap_getPointerPosition(PyObject *self, PyObject *args) { int win; Window w; Bool inScreen; Window root, child; int root_x, root_y, win_x, win_y; unsigned int mask; Bool XQueryPointer(Display *, Window, Window *, Window *, int *, int *, int *, int *, unsigned int *); if (!PyArg_ParseTuple(args,"i", &win)) return NULL; w = (Window) win; TrapXlibErrors /* macro code to handle xlib exceptions */ if (d == NULL) { printf("could not open XWindow display\n"); RestoreOldXlibErrorHandlers /* macro */ return NULL; } inScreen = XQueryPointer(d, w, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask); RestoreOldXlibErrorHandlers /* macro */ return Py_BuildValue("{s:i,s:i,s:i,s:i,s:i,s:i,s:i}", "inScreen", (int) inScreen, "rootID", (int) root, "childID", (int) child, "root_x", root_x, "root_y", root_y, "win_x", win_x, "win_y", win_y); } PyObject *wrap_getParentID(PyObject *self, PyObject *args) { int win; Window w; Window root, parent, *children; unsigned int nchildren; /* Display *XOpenDisplay(char *); */ Status XQueryTree(Display *, Window, Window *, Window *, Window **, unsigned int *); if (!PyArg_ParseTuple(args,"i", &win)) return NULL; w = (Window) win; if ((w==PointerRoot) | (w==None)) { return Py_BuildValue("i", (int) w); } TrapXlibErrors /* macro code to handle xlib exceptions */ if (d == NULL) { printf("could not open XWindow display\n"); RestoreOldXlibErrorHandlers /* macro */ return NULL; } XQueryTree(d, w, &root, &parent, &children, &nchildren); XFree(children); RestoreOldXlibErrorHandlers /* macro */ if (root == parent) { Py_INCREF(Py_None); return Py_None; } else { return Py_BuildValue("i", (int) parent); } } void initXGraphics(void) { d = XOpenDisplay(NULL); if (d == NULL) { printf("could not open XWindow display\n"); return; } screen_num = DefaultScreen(d); } PyObject *wrap_initXGraphics(PyObject *self, PyObject *args) { TrapXlibErrors /* macro code to handle xlib exceptions */ initXGraphics(); RestoreOldXlibErrorHandlers /* macro */ Py_INCREF(Py_None); return Py_None; } void closeXGraphics(void) { XCloseDisplay(d); } PyObject *wrap_closeXGraphics(PyObject *self, PyObject *args) { TrapXlibErrors /* macro code to handle xlib exceptions */ closeXGraphics(); RestoreOldXlibErrorHandlers /* macro */ Py_INCREF(Py_None); return Py_None; } void drawCursor(int win, double x, double y, int width, int height) { /* plot cursor at x and y where x,y are normalized (range from 0 to 1) */ Window w; w = (Drawable) win; if (d == NULL) { printf("could not open XWindow display\n"); return; } /* only get a graphics context if the window id changes */ if (win != last_win) { last_win = win; gc = XCreateGC(d, w, 0, NULL); if (!XGetWindowAttributes(d, w, &wa)) { printf("Problem getting window attributes\n"); return; } cmap = wa.colormap; if (!XParseColor(d, cmap, "red", &colorfg)) { printf("could not parse color string\n"); return; } if (!XParseColor(d, cmap, "black", &colorbg)) { printf("could not parse color string\n"); return; } if (!(XAllocColor(d, cmap, &colorfg) && XAllocColor(d, cmap, &colorbg))) { printf("Problem allocating colors for cursor color determination\n"); return; } color.pixel = colorfg.pixel ^ colorbg.pixel; XSetFunction(d, gc, GXxor); XSetForeground(d, gc, color.pixel); } /*if (!XGetGeometry(d,w,&wroot, &xr, &yr, &width, &height, &border, &depth)) { printf("could not get window geometry\n"); return; }*/ XDrawLine(d, w, gc, (int) (x*width), 0, (int) (x*width), height); XDrawLine(d, w, gc, 0, (int) ((1.-y)*height), width, (int) ((1.-y)*height)); XFlush(d); } PyObject *wrap_drawCursor(PyObject *self, PyObject *args) { double x, y; int width, height; int win; if (!PyArg_ParseTuple(args, "iddii", &win, &x, &y, &width, &height)) return NULL; TrapXlibErrors /* macro code to handle xlib exceptions */ drawCursor(win, x, y, width, height); RestoreOldXlibErrorHandlers /* macro */ Py_INCREF(Py_None); return Py_None; } static PyMethodDef xutil_funcs[] = { { "moveCursorTo",wrap_moveCursorTo, 1}, { "getFocalWindowID",wrap_getFocalWindowID, 1}, { "setFocusTo",wrap_setFocusTo, 1}, { "setBackingStore",wrap_setBackingStore, 1}, { "getWindowAttributes",wrap_getWindowAttributes, 1}, { "getPointerPosition",wrap_getPointerPosition, 1}, { "getParentID",wrap_getParentID, 1}, { "getDeepestVisual", wrap_getDeepestVisual, 1}, { "initXGraphics",wrap_initXGraphics, 1}, { "closeXGraphics",wrap_closeXGraphics, 1}, { "drawCursor",wrap_drawCursor, 1}, {NULL, NULL} }; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "xutil", NULL, -1, xutil_funcs, NULL, NULL, NULL, NULL, }; PyObject* PyInit_xutil(void) { return PyModule_Create(&moduledef); } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.664998 pyraf-2.2.1/pyraf.egg-info/0000755000175000017500000000000014223000071014163 5ustar00olesoles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf.egg-info/PKG-INFO0000644000175000017500000001006414223000070015260 0ustar00olesolesMetadata-Version: 2.1 Name: pyraf Version: 2.2.1 Summary: Pythonic interface to IRAF that can be used in place of the existing IRAF CL Home-page: https://iraf-community.github.io/pyraf.html Author: Rick White, Perry Greenfield, Chris Sontag, Ole Streicher License: BSD 3-Clause Keywords: astronomy,astrophysics,utility Platform: any Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Scientific/Engineering :: Astronomy Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: docs Provides-Extra: test License-File: LICENSE.txt ===== PyRAF ===== |Release| |CI Status| |Coverage Status| |Documentation| PyRAF is a command language for running IRAF tasks in a Python like environment. It works very similar to IRAF, but has been updated to allow such things as importing Python modules, starting in any directory, GUI parameter editing and help. Most importantly, it can be imported into Python allowing you to run IRAF commands from within a larger script. Installation ------------ To install PyRAF, it is required to have both IRAF_ and Python_ already installed. For the IRAF installation, both a self-compiled and a binary IRAF package (f.e. in Ubuntu_) will work. The IRAF installation should have a properly configured environment, especially the ``iraf`` environment variable must be set to point to the IRAF installation directory (i.e. to ``/usr/lib/iraf/`` on Ubuntu or Debian systems). On multi-arch IRAF installations, the ``IRAFARCH`` environment variable should specify the architecture to use. This is usually already set during the IRAF installation procedure. The minimal Python required for PyRAF is 3.6, but it is recommended to use the latest available version. An installation in an virtual environment like venv_ or conda_ is possible. The package can be installed from PyPI_ with the command ``pip3 install pyraf``. Note that if no binary installation is available on PyPI, the package requires a compilation, so aside from pip3, the C compiler and development libraries (on Linux ``libx11-dev``) should be installed. Contributing Code, Documentation, or Feedback --------------------------------------------- IRAF and PyRAF can only survive by the contribution of its users, so we welcome and encourage your contributions. The preferred way to report a bug is to create a new issue on the `PyRAF GitHub issue `_ page. To contribute patches, we suggest to create a `pull request on GitHub `_. License ------- PyRAF is licensed under a 3-clause BSD style license - see the `LICENSE.txt `_ file. Documentation ------------- * `The PyRAF Tutorial `_ .. |CI Status| image:: https://github.com/iraf-community/pyraf/actions/workflows/citest.yml/badge.svg :target: https://github.com/iraf-community/pyraf/actions :alt: Pyraf CI Status .. |Coverage Status| image:: https://codecov.io/gh/iraf-community/pyraf/branch/main/graph/badge.svg :target: https://codecov.io/gh/iraf-community/pyraf :alt: PyRAF Coverage Status .. |Release| image:: https://img.shields.io/github/release/iraf-community/pyraf.svg :target: https://github.com/iraf-community/pyraf/releases/latest :alt: Pyraf release .. |Documentation| image:: https://readthedocs.org/projects/pyraf/badge/?version=latest :target: https://pyraf.readthedocs.io/en/latest/ :alt: Documentation Status .. _Python: https://www.python.org/ .. _venv: https://docs.python.org/3/library/venv.html .. _conda: https://docs.conda.io/ .. _PyPI: https://pypi.org/project/pyraf .. _IRAF: https://iraf-community.github.io .. _iraf-community: https://iraf-community.github.io .. _Ubuntu: https://www.ubuntu.com/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf.egg-info/SOURCES.txt0000644000175000017500000000633214223000070016052 0ustar00olesoles.gitignore .readthedocs.yaml CODE_OF_CONDUCT.md LICENSE.txt MANIFEST.in README.rst setup.cfg setup.py .github/workflows/citest.yml docs/conf.py docs/ecl.html docs/epar.png docs/index.rst docs/ipython_notes.txt docs/prow.png docs/pyraf_tutorial.rst docs/teal_guide.rst docs/_static/brand.css docs/_static/pyraflogo.png pyraf/GkiMpl.py pyraf/MplCanvasAdapter.py pyraf/Ptkplot.py pyraf/__init__.py pyraf/__main__.py pyraf/aqutil.py pyraf/blankcursor.xbm pyraf/cgeneric.py pyraf/cl2py.py pyraf/clast.py pyraf/clcache.py pyraf/cllinecache.py pyraf/clparse.py pyraf/clscan.py pyraf/cltoken.py pyraf/epar.optionDB pyraf/epar.py pyraf/filecache.py pyraf/fill_clcache.py pyraf/fontdata.py pyraf/generic.py pyraf/gki.py pyraf/gkicmd.py pyraf/gkigcur.py pyraf/gkiiraf.py pyraf/gkitkbase.py pyraf/gkitkplot.py pyraf/graphcap.py pyraf/gwm.py pyraf/ipython_api.py pyraf/ipythonrc-pyraf pyraf/iraf.py pyraf/irafcompleter.py pyraf/irafdisplay.py pyraf/irafecl.py pyraf/irafexecute.py pyraf/iraffunctions.py pyraf/irafgwcs.py pyraf/irafhelp.py pyraf/irafimcur.py pyraf/irafimport.py pyraf/irafinst.py pyraf/irafnames.py pyraf/irafpar.py pyraf/iraftask.py pyraf/irafukey.py pyraf/msgiobuffer.py pyraf/msgiowidget.py pyraf/newWindowHack.py pyraf/pseteparoption.py pyraf/pycmdline.py pyraf/pyrafTk.py pyraf/pyrafglobals.py pyraf/pyraflogo_rgb_web.gif pyraf/splash.py pyraf/sqliteshelve.py pyraf/sscanfmodule.c pyraf/subproc.py pyraf/textattrib.py pyraf/tkplottext.py pyraf/tpar.py pyraf/urwfiledlg.py pyraf/urwutil.py pyraf/version.py pyraf/wutil.py pyraf/xutil.c pyraf.egg-info/PKG-INFO pyraf.egg-info/SOURCES.txt pyraf.egg-info/dependency_links.txt pyraf.egg-info/entry_points.txt pyraf.egg-info/not-zip-safe pyraf.egg-info/requires.txt pyraf.egg-info/top_level.txt pyraf/noiraf/cl.par pyraf/noiraf/clpackage.cl pyraf/noiraf/login.cl pyraf/noiraf/system.cl pyraf/noiraf/system.par pyraf/tests/__init__.py pyraf/tests/test_basic.py pyraf/tests/test_clcache.py pyraf/tests/test_cli.py pyraf/tests/test_core_nongraphics.py pyraf/tests/test_graphics.py pyraf/tests/test_invocation.py pyraf/tests/test_plot.py pyraf/tests/test_tasks.py pyraf/tests/test_using_tasks.py pyraf/tests/utils.py pyraf/tests/data/psdump_prow_250.ps pyraf/tests/data/psdump_prow_256.ps pyraf/tests/data/psdump_prow_256_250.ps pyraf/tests/data/psdump_prow_256_250_200.ps pyraf/tests/data/pset_msstat_input.fits pyraf/tests/data/psi_land_prow_250.ps pyraf/tests/data/psi_land_prow_256.ps pyraf/tests/data/psi_land_prow_256_250.ps pyraf/tests/data/psi_land_prow_256_250_200.ps pyraf/tools/__init__.py pyraf/tools/alert.py pyraf/tools/basicpar.py pyraf/tools/capable.py pyraf/tools/cfgpars.py pyraf/tools/compmixin.py pyraf/tools/dialog.py pyraf/tools/editpar.py pyraf/tools/eparoption.py pyraf/tools/filedlg.py pyraf/tools/irafglobals.py pyraf/tools/irafutils.py pyraf/tools/listdlg.py pyraf/tools/minmatch.py pyraf/tools/taskpars.py pyraf/tools/teal.py pyraf/tools/teal_bttn.py pyraf/tools/tkrotext.py pyraf/tools/vtor_checks.py pyraf/tools/tests/__init__.py pyraf/tools/tests/cfgobj_output.ref pyraf/tools/tests/rt_sample.cfg pyraf/tools/tests/rt_sample.cfgspc pyraf/tools/tests/test_cfgobj.py pyraf/tools/tests/test_compmixin.py pyraf/tools/tests/test_irafutils.py pyraf/tools/tests/test_minmatch.py tools/compileallcl.py tools/loadall.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf.egg-info/dependency_links.txt0000644000175000017500000000000114223000070020230 0ustar00olesoles ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf.egg-info/entry_points.txt0000644000175000017500000000004614223000070017460 0ustar00olesoles[console_scripts] pyraf = pyraf:main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf.egg-info/not-zip-safe0000644000175000017500000000000114223000070016410 0ustar00olesoles ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf.egg-info/requires.txt0000644000175000017500000000012414223000070016557 0ustar00olesolesconfigobj numpy [docs] astropy-sphinx-theme numpydoc sphinx [test] astropy pytest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1649147960.0 pyraf-2.2.1/pyraf.egg-info/top_level.txt0000644000175000017500000000006014223000070016710 0ustar00olesolespyraf pyraf/tests pyraf/tools pyraf/tools/tests ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.676998 pyraf-2.2.1/setup.cfg0000644000175000017500000000301514223000071013170 0ustar00olesoles[metadata] name = pyraf author = Rick White, Perry Greenfield, Chris Sontag, Ole Streicher url = https://iraf-community.github.io/pyraf.html description = Pythonic interface to IRAF that can be used in place of the existing IRAF CL long_description = file:README.rst long_description_content_type = text/x-rst keywords = astronomy, astrophysics, utility edit_on_github = True github_project = iraf-community/iraf platform = any license = BSD 3-Clause license_file = LICENSE.txt classifiers = Intended Audience :: Science/Research License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Topic :: Scientific/Engineering :: Astronomy [options] setup_requires = setuptools_scm install_requires = numpy configobj packages = pyraf pyraf/tests pyraf/tools pyraf/tools/tests zip_safe = False python_requires = >=3.6 [options.extras_require] docs = sphinx numpydoc astropy-sphinx-theme test = pytest astropy [options.entry_points] console_scripts = pyraf = pyraf:main [options.package_data] pyraf = blankcursor.xbm, epar.optionDB, pyraflogo_rgb_web.gif, ipythonrc-pyraf pyraf/tests = data/* pyraf/tools/tests = * [tool:pytest] minversion = 3.0 addopts = -s [flake8] ignore = [yapf] based_on_style = google column_limit = 79 [coverage:run] source = pyraf omit = pyraf/tests/* [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/setup.py0000755000175000017500000000122614203121554013076 0ustar00olesoles#! /usr/bin/env python3 import os import platform from setuptools import setup, Extension modules = [Extension('pyraf.sscanf', ['pyraf/sscanfmodule.c'])] # On a Mac, xutil is not required since the graphics is done directly with Aqua. # If one still wants to include it, the parameters # library_dirs="/usr/X11/libs" # include_dirs="/usr/X11/include" # need to be added to the Extension if platform.system() not in ('Darwin', 'Windows'): modules.append(Extension('pyraf.xutil', ['pyraf/xutil.c'], libraries=['X11'])) setup(ext_modules=modules, use_scm_version={'write_to': os.path.join('pyraf', 'version.py')}) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1649147960.672998 pyraf-2.2.1/tools/0000755000175000017500000000000014223000071012510 5ustar00olesoles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/tools/compileallcl.py0000644000175000017500000001516214203121554015537 0ustar00olesoles#! /usr/bin/env python3 """compileallcl.py: Load all the packages in IRAF and compile all the CL scripts The results are stored in the user cache. The old user cache is moved to ~/iraf/pyraf/clcache.old. Set the -r flag to recompile (default is to close the system cache and move the user cache so that everything gets compiled from scratch.) """ import os import sys import traceback import time def printcenter(s, length=70, char="-"): l1 = (length - len(s)) // 2 l2 = length - l1 - len(s) print(l1 * char, s, l2 * char) sys.stdout.flush() def compileall(): """Compile all CL procedures & packages""" # start the timer t0 = time.time() # now do the IRAF startup from pyraf import iraf, cl2py, clcache, irafglobals from pyraf.iraftask import IrafCLTask, IrafPkg # close the old code cache and reopen without system cache cl2py.codeCache.close() del clcache.codeCache userCacheDir = os.path.join(irafglobals.userIrafHome, 'pyraf') if not os.path.exists(userCacheDir): try: os.mkdir(userCacheDir) print('Created directory %s for cache' % userCacheDir) except OSError: print('Could not create directory %s' % userCacheDir) dbfile = 'clcache' clcache.codeCache = clcache._CodeCache( [os.path.join(userCacheDir, dbfile)]) cl2py.codeCache = clcache.codeCache iraf.setVerbose() pkgs_tried = {} tasks_tried = {} npkg_total = 0 ntask_total = 0 ntask_failed = 0 # main loop -- keep loading packages as long as there are # new ones to try, and after loading each package look for # and initialize any CL tasks npass = 0 pkg_list = iraf.getPkgList() keepGoing = 1 while keepGoing and (npkg_total < len(pkg_list)): npass = npass + 1 pkg_list.sort() npkg_new = 0 printcenter("pass %d: %d packages (%d new)" % (npass, len(pkg_list), len(pkg_list) - npkg_total), char="=") for pkg in pkg_list: if pkg not in pkgs_tried: pkgs_tried[pkg] = 1 npkg_new = npkg_new + 1 printcenter(pkg) if pkg in ["newimred", "digiphotx"]: print(""" Working around bugs in newimred, digiphotx. They screw up subsequent loading of imred/digiphot tasks. (It happens in IRAF too.)""") sys.stdout.flush() else: try: # redirect stdin in case the package tries to # prompt for parameters (this aborts but keeps # going) iraf.load(pkg, kw={'Stdin': 'dev$null'}) except KeyboardInterrupt: print('Interrupt') sys.stdout.flush() keepGoing = 0 break except Exception as e: sys.stdout.flush() traceback.print_exc() if isinstance(e, MemoryError): keepGoing = 0 break print("...continuing...\n") sys.stdout.flush() # load tasks after each package task_list = sorted(iraf.getTaskList()) for taskname in task_list: if taskname not in tasks_tried: tasks_tried[taskname] = 1 taskobj = iraf.getTask(taskname) if isinstance(taskobj, IrafCLTask) and \ not isinstance(taskobj, IrafPkg): ntask_total = ntask_total + 1 print("%d: %s" % (ntask_total, taskname)) sys.stdout.flush() try: taskobj.initTask() except KeyboardInterrupt: print('Interrupt') sys.stdout.flush() keepGoing = 0 break except Exception as e: sys.stdout.flush() traceback.print_exc(10) if isinstance(e, MemoryError): keepGoing = 0 break print("...continuing...\n") sys.stdout.flush() ntask_failed = ntask_failed + 1 if not keepGoing: break npkg_total = npkg_total + npkg_new if not keepGoing: break printcenter( "Finished pass %d new pkgs %d total pkgs %d total tasks %d" % (npass, npkg_new, npkg_total, ntask_total), char="=") pkg_list = iraf.getPkgList() t1 = time.time() print("Finished package and task loading ({:f} seconds)".format(t1 - t0)) print("Compiled %d CL tasks -- %d failed" % (ntask_total, ntask_failed)) sys.stdout.flush() def usage(): print("""Usage: %s [-r] [-h] -r recompiles only out-of-date routines. Default is to force recompilation of all CL scripts and packages. -h prints this message. """) sys.stdout.flush() sys.exit() if __name__ == '__main__': # command-line options import getopt try: optlist, args = getopt.getopt(sys.argv[1:], "rh") if len(args) > 0: print("Error: no positional arguments accepted") usage() except getopt.error as e: print(str(e)) usage() clearcache = 1 for opt, value in optlist: if opt == "-r": clearcache = 0 elif opt == "-h": usage() else: print("Program bug, unknown option", opt) raise SystemExit() # find pyraf directory for p in sys.path: absPyrafDir = os.path.join(p, 'pyraf') if os.path.exists(os.path.join(absPyrafDir, '__init__.py')): break else: raise Exception('pyraf module not found') usrCache = os.path.expanduser('~/iraf/pyraf/clcache') if clearcache: # move the old user cache if os.path.exists(usrCache): os.rename(usrCache, usrCache + '.old') print("Pyraf user cache %s renamed to %s.old" % (usrCache, usrCache)) else: print("Warning: pyraf user cache %s was not found" % usrCache) compileall() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1644995436.0 pyraf-2.2.1/tools/loadall.py0000644000175000017500000000332414203121554014504 0ustar00olesoles#! /usr/bin/env python3 """loadall.py: Load all the main packages in IRAF with verbose turned on """ import sys import traceback from pyraf import iraf iraf.setVerbose() def printcenter(s, length=70, char="-"): l1 = (length - len(s)) // 2 l2 = length - l1 - len(s) print(l1 * char, s, l2 * char) ptried = {} npass = 0 ntotal = 0 plist = iraf.getPkgList() keepGoing = 1 while keepGoing and (ntotal < len(plist)): plist.sort() nnew = 0 npass = npass + 1 printcenter("pass " + repr(npass) + " trying " + repr(len(plist)), char="=") for pkg in plist: if pkg not in ptried: ptried[pkg] = 1 nnew = nnew + 1 l1 = (70 - len(pkg)) // 2 l2 = 70 - l1 - len(pkg) printcenter(pkg) if pkg == "digiphotx": print(""" Working around bug in digiphotx. It screws up subsequent loading of digiphot tasks. (It happens in IRAF too.)""") else: try: iraf.load(pkg) except KeyboardInterrupt: print('Interrupt') keepGoing = 0 break except Exception as e: sys.stdout.flush() traceback.print_exc() if e.__class__ == MemoryError: keepGoing = 0 break print("...continuing...\n") ntotal = ntotal + nnew printcenter("Finished pass " + repr(npass) + " new pkgs " + repr(nnew) + " total pkgs " + repr(ntotal), char="=") plist = iraf.getPkgList()