source: SHX/trunk/SeismicHandler/modules/parse.py @ 530

Revision 530, 28.8 KB checked in by marcus, 11 years ago (diff)
  • double variable substitution to allow commands like 'echo #1("cnt)'
  • Property svn:eol-style set to native
Line 
1# -*- coding: utf-8 -*-
2
3#    This file is part of Seismic Handler eXtended (SHX).
4#
5#    SHX is free software: you can redistribute it and/or modify
6#    it under the terms of the GNU Lesser General Public License as published
7#    by the Free Software Foundation, either version 3 of the License, or
8#    (at your option) any later version.
9#
10#    SHX is distributed in the hope that it will be useful,
11#    but WITHOUT ANY WARRANTY; without even the implied warranty of
12#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#    GNU Lesser General Public License for more details.
14#
15#    You should have received a copy of the GNU Lesser General Public License
16#    along with SHX.  If not, see <http://www.gnu.org/licenses/>.
17
18"""
19These classes cover the processing of SH scripting language.
20
21- "script" processes a stream (open file, stringIO, ...) of commands
22- "parse" parses one command
23- "translate" manages variable substitution
24- "symbol" cares for local and global symbols
25
26It's intended to be compatible with the original C code SH. Please file a ticket
27at http://www.seismic-handler.org/ if there's something wrong or missing.
28"""
29
30import os
31import re
32import inspect
33import traceback
34from copy import copy
35import SeismicHandler.commands as commands
36from SeismicHandler.basics import AttribDict
37from SeismicHandler.basics.error import ShxError
38from SeismicHandler.config import Settings
39from SeismicHandler.basics.messages import logMessage, msgs
40from SeismicHandler.core import Traces, Hidden
41
42
43class Parse(object):
44    """
45    Parse SH scripting language.
46
47    After successful parsing these attributes are set:
48    - input (original input),
49    - input_conv (maybe changed to upper case, depends on global setting),
50    - parameter
51    - qualifier
52    - suspectedFilename (unix filenames contain slashes, it just a guess)
53
54    These values can be also accessed via:
55    - class attribute "parsed" (dictionary)
56    - class attribute with "shx_" prefix (e.g. shx_input)
57
58    The parser analyses the command string:
59    >>> cmd = 'COMMAND /SWITCH1 PARAMETER1 /SWITCH2=4 PARAMETER2'
60    >>> x = Parse(cmd).parsed
61
62    The parsed command name is always lower case. It's used for comparision with
63    the "provides" attribute of the command classes.
64    >>> x["shx_command"]
65    'command'
66
67    Switches may occur at any position, but are commonly placed following the
68    command name:
69    >>> sorted(x["shx_qualifiers"].keys())
70    ['SWITCH1', 'SWITCH2']
71    >>> x["shx_qualifiers"]["SWITCH1"]
72    True
73    >>> x["shx_qualifiers"]["SWITCH2"]
74    '4'
75
76    Parameter are returned as list in the order of occurrence:
77    >>> x["shx_parameter"]
78    ['PARAMETER1', 'PARAMETER2']
79
80    One new feature is that the algorithm guesses a file name (slashes normally
81    indicate qualifiers). This only works, if the switches follow directly after
82    the command name!
83    >>> cmd = 'DELAY+SUM/BLA/FOO=1 BAR /tmp/test /SW FOO'
84    >>> x = Parse(cmd).parsed
85    >>> x["shx_parameter"]
86    ['BAR', 'FOO']
87    >>> x["shx_qualifiers"]["FOO"]
88    '1'
89
90    Please note that the "suspected" file name is also present as switch:
91    >>> sorted(x["shx_qualifiers"].keys())
92    ['BLA', 'FOO', 'SW', 'TEST', 'TMP']
93    >>> x["shx_suspectedFilename"]
94    '/TMP/TEST'
95
96    If more than one "possible filename is given, only the first one is
97    recognized. All following are only processed as qualifiers.
98    >>> x = Parse('COMMAND /tmp/x /tmp/y').parsed
99    >>> x["shx_suspectedFilename"]
100    '/TMP/X'
101
102    The semicolon is an alternative separator that is mostly used to skip
103    parameters:
104    >>> x = Parse("ECHO;;2 3 4 ;; 6 7").parsed
105    >>> x["shx_parameter"]
106    ['', '2', '3', '4', '', '6', '7']
107
108    >>> x = Parse('ECHO;;;;foo;bar;;').parsed
109    >>> x["shx_parameter"]
110    ['', '', '', 'FOO', 'BAR', '']
111    """
112
113    # regular expressions for parsing
114    re_cmd = "[+\w]+"
115    re_qual = "/[\w=]+"
116    re_file = " (/\w+)+ ?"
117
118    def __init__(self, input_string, switches=None):
119        if switches is None:
120            switches = Switches()
121
122        self.switches = switches
123        # treat everything after ! as comment
124        input_string = input_string.split("!")[0]
125        self.input = input_string
126
127        converted = False
128        if self.switches.Capcnv:
129            # If a command starts with @, no conversion to upper case is
130            # performed but the indicator char is removed.
131            if input_string.startswith("@"):
132                input_string = input_string[1:]
133            else:
134                input_string = input_string.upper()
135                converted = True
136
137        c = re.compile(self.re_cmd)
138        q = re.compile(self.re_qual)
139        f = re.compile(self.re_file)
140
141        cmd = c.search(input_string).group(0)
142        qual = q.findall(input_string)
143
144        # guess file
145        try:
146            sfile = f.search(input_string).group(0).strip()
147        except:
148            sfile = None
149
150        qualifiers = {}
151        # remove cmd and qualifiers from string
152        cmd_par = input_string.replace(cmd, "")
153        for qq in qual:
154            cmd_par = cmd_par.replace(qq, "")
155
156            # build dict of qualifiers
157            i = qq.split("=")
158            if len(i) > 1:
159                qualifiers[i[0][1:]] = i[1]
160            else:
161                qualifiers[i[0][1:]] = True
162
163        par = cmd_par.split()
164
165        # check for semicolon placeholders
166        parameter = []
167        for j in par:
168            if ";" in j:
169                j = j.split(";")
170
171                # correct leading and trailing
172                if j[:2] == ['','']:
173                    j = j[1:]
174                if j[-2:] == ['','']:
175                    j = j[:-1]
176
177                parameter.extend(j)
178            else:
179                parameter.append(j)
180
181        self._parsed = {
182            "shx_input": self.input,
183            "shx_input_conv": input_string,
184            "shx_command": cmd.lower(), # command always in lower case
185            "shx_converted": converted, # indicates cap conversion
186            "shx_parameter": parameter,
187            "shx_qualifiers": qualifiers,
188            "shx_suspectedFilename": sfile,
189        }
190
191    def __getattr__(self, name):
192        try:
193            p = object.__getattribute__(self, "_parsed")
194
195            if name.startswith("shx_"):
196                return p[name]
197
198            if "shx_%s" % name in p.keys():
199                return p["shx_%s" % name]
200
201            if name == "parsed":
202                return p
203        except AttributeError:
204            pass
205
206        try:
207            return object.__getattribute__(self, name)
208        except AttributeError:
209            raise ShxError("Attribute '%s' not found!" % name)
210
211
212class Script(object):
213    """
214    Read commands from stream.
215
216    GOTO targets are cached and removed from stream so that any GOTO command
217    must be handled inside this class.
218
219    Also the IF condition is evaluated here.
220
221    All other commands are promoted to their handler or treated as new script.
222
223    For testing purposes we use a stream and fill it with commands. Also a
224    symbol set is needed. To echo the commands, the global "Echo" switch must
225    be turned on.
226    >>> from StringIO import StringIO
227    >>> strm = StringIO("echo line1\\necho line2\\n")
228    >>> symb = Symbol()
229    >>> sw = Switches()
230    >>> sw.Echo = True
231    >>> x = Script(strm, symb, sw)
232    >>> x.run()
233    echo line1
234    echo line2
235
236    It's possible to add more commands at runtime. The script will continue
237    there.
238    >>> x.feed(StringIO("echo line3\\n"))
239    >>> x.run()
240    echo line3
241    """
242
243    def __init__(self, inputdata, symbols, switches, parameters=None, **kwargs):
244        """
245        Read in stream, skip empty lines and comments. Remember GOTO targets.
246
247        Input parameters are:
248        - stream object having readline function
249        - symbol class instance (containing global and local variables
250        - switches class instance
251        """
252
253        self.content = []
254        self.targets = {}
255
256        self.pointer = 0
257        self.symbols = symbols
258        self.switches = switches
259
260        # If a script is initially called there might be parameters and
261        # also qualifiers.
262        if parameters:
263            self.parameters = parameters
264
265        # current working directory
266        self.searchpath = ["."]
267
268        # additional paths
269        if "searchpath" in kwargs:
270            self.searchpath += kwargs["searchpath"]
271
272        # use default search path from configuration
273        self.searchpath += Settings.config.paths.scripts
274
275        try:
276            if hasattr(inputdata, "read"):
277                stream = inputdata
278            elif type(inputdata) == str:
279                # no extension -> add default
280                try:
281                    _ = inputdata.index(".")
282                except ValueError:
283                    inputdata = inputdata + ".SHC"
284
285                if self.switches.Capcnv:
286                    inputdata = inputdata.upper()
287
288                self.filename = inputdata
289
290                # look for file in search path
291                for folder in self.searchpath:
292                    try:
293                        stream = open(os.path.join(folder, inputdata), "r")
294                    except IOError:
295                        pass
296                    else:
297                        break
298
299            _ = stream
300        except:
301            # cannot open
302            raise ShxError("Input not readable!")
303
304        # Cache self-handled commands. These methods begin with "command".
305        commands = []
306        for i in inspect.getmembers(self, lambda x: inspect.ismethod(x)):
307            if i[0].startswith("command"):
308                commands.append(i[0][7:].lower())
309
310        self.internal_commands = commands
311
312        self.feed(stream)
313
314    def feed(self, stream):
315        """
316        Method for populating command list.
317
318        This method can be run several times in order to run commands from
319        interactive input. The local symbol set is hereby not changed.
320
321        Execution of commands will automatically continue after the last
322        command.
323
324        So it's no possible to use loops also in interactive scripts. But be
325        warned: It's not easy to get a proper exit GOTO target!
326        """
327
328        content = self.content
329        targets = self.targets
330        skipped = 0
331
332        for line, cmdstr in enumerate(stream):
333            cmdstr = cmdstr.strip()
334
335            # skip empty lines or comments
336            if len(cmdstr) and cmdstr[0] not in "!#-":
337                # remember goto target line?
338                if cmdstr.split()[0][-1] == ":":
339                    targets[cmdstr.upper()] = line - skipped
340
341                content.append(cmdstr)
342            else:
343                skipped += 1
344
345        self.content = content
346        self.targets = targets
347
348    def traceBack(self, error, msg = ""):
349        # This allows debugging as python users are used to it.
350        print "\n" + "*"*72
351        print self.__recent
352        print "*"*72
353        traceback.print_exc()
354        print "*"*72
355        msg = ";".join(error.args)
356
357        # Respect global settings:
358        if self.switches.Sherrstop:
359            import sys
360            print >> sys.stderr, error, msg
361            quit()
362
363        if self.switches.Cmderrstop:
364            raise ShxError(msg)
365
366        if not self.switches.Noerrmsg:
367            logMessage("error.traceback", msg)
368
369    def run(self):
370        self.__recent = None
371        while True:
372            try:
373                cmd = self.next()
374            except StopIteration:
375                break
376
377            if self.switches.Echo:
378                print cmd
379
380            cmd = Parse(cmd, switches=self.switches).parsed
381
382            # translate variables
383            _ = Translate(cmd, self, switches=self.switches)
384            self.__recent = cmd.get("shx_translated", cmd["shx_input_conv"])
385
386            # Check for internal command ...
387            if cmd["shx_command"] in self.internal_commands:
388                try:
389                    x = getattr(self, "command"+cmd["shx_command"].capitalize())
390                    x(*cmd["shx_parameter"], **cmd["shx_qualifiers"])
391                except Exception, e:
392                    self.traceBack(e)
393
394            # or execute command...
395            elif cmd["shx_command"] in commands.list:
396                if self.switches.Verify:
397                    print cmd["shx_translated"]
398
399                try:
400                    # also supply recent symbolset
401                    commands.list[cmd["shx_command"]](
402                        shx_symbols=self.symbols,
403                        shx_switches=self.switches,
404                        *cmd["shx_parameter"],
405                        **cmd["shx_qualifiers"]
406                    )
407                except Exception, e:
408                    self.traceBack(e)
409
410            # .. or start script.
411            else:
412                # use new symbols and switches set
413                symb = Symbol()
414                swit = Switches()
415                try:
416                    ns = Script(
417                        cmd["shx_command"],
418                        symb,
419                        swit,
420                        parameters=cmd,
421                        searchpath=self.searchpath,
422                    )
423                    ns.run()
424                except Exception, e:
425                    msg = "Cannot run script '%s'!" % cmd["shx_command"]
426                    self.traceBack(e, msg)
427
428    def next(self):
429        """
430        Iterate over commands.
431        """
432
433        try:
434            # skip goto targets
435            while self.pointer in self.targets.values():
436                self.pointer += 1
437
438            pnt = self.pointer
439            self.pointer += 1
440
441            return self.content[pnt]
442        except IndexError:
443            # lower pointer
444            self.pointer -= 1
445            raise StopIteration
446
447    def commandReturn(self):
448        # jump to end of script
449        self.pointer = len(self.content)
450
451    def commandDefault(self, *args):
452        """
453        Query default values.
454
455        Since this statement manipulates script option variables, it's not done
456        as command class.
457
458        Note: No qualifiers are supported! Input is read from stdin.
459        """
460
461        # no parameters given at all -> request user input
462        # set as class attribute for caching.
463        try:
464            self.userreq = self.userreq and True or \
465                                     len(self.parameters["shx_parameter"]) == 0
466        except AttributeError:
467            self.userreq = len(self.parameters["shx_parameter"]) == 0
468
469        id = int(args[0])-1
470        default = args[1]
471
472        val = None
473        try:
474            val = self.parameters["shx_parameter"][id]
475        except IndexError:
476            if self.userreq:
477                # request user input
478                if len(default):
479                    q = " [default: %s]: " % default
480                else:
481                    q = " : "
482
483                userinput = raw_input(" ".join(args[2:]) + q)
484                if userinput:
485                    default = userinput
486
487        if not val:
488            # fill from default
489            try:
490                self.parameters["shx_parameter"][id] = default
491            except IndexError:
492                self.parameters["shx_parameter"].append(default)
493
494    def commandGoto(self, target):
495        try:
496            self.pointer = self.targets[target.upper()]
497            msgs.sendMessage("command.run", cmd="goto", args=[target,])
498        except KeyError:
499            raise ShxError("GOTO target not found: %s" % target.upper())
500
501    def commandQuit(self, *args):
502        if not args:
503            yesno = raw_input("  Leave program [yN]? ")
504        else:
505            yesno = args[0]
506
507        msgs.sendMessage("command.run", cmd="quit")
508        if yesno.lower() == "y":
509            quit()
510
511    def commandIf(self, cmd):
512        pass
513
514    def IfGoto(self, cmd):
515        # cast format
516        cast = {
517                "S": str,
518                "I": int,
519                "R": float
520                }
521
522        cmp, check = cmd.p[2].value, cmd.p[2].value[:2]
523
524        var1 = cast[cmp[-1]](cmd.p[1].value)
525        var2 = cast[cmp[-1]](cmd.p[3].value)
526
527        # comparison
528        try:
529            comp = {
530                    "EQ": var1.__eq__,
531                    "NE": var1.__ne__,
532                    "GT": var1.__gt__,
533                    "GE": var1.__ge__,
534                    "LT": var1.__lt__,
535                    "LE": var1.__le__,
536                    }
537        except AttributeError:
538            # in python 2.5 integers have no __eq__, __gt__, ... methods :(
539            comp = {
540                    "EQ": lambda x: var1 == x,
541                    "NE": lambda x: var1 != x,
542                    "GT": lambda x: var1 > x,
543                    "GE": lambda x: var1 >= x,
544                    "LT": lambda x: var1 < x,
545                    "LE": lambda x: var1 <= x,
546                    }
547
548        # check condition
549        if comp[check](var2):
550            self.Goto(cmd.p[5].value)
551
552
553class Translate(object):
554    """
555    Translate variables in command. If a script class is used as second
556    parameter, it's symbol set will be used (e.g. for global symbols).
557
558    There are five basic types of variables:
559    1. user-defined symbols start with a quote: "foo
560    2. system variables start with a dollar sign: $DSPTRCS
561    3. trace variables start with a caret: ^delta(3)
562    4. passed options to command procedures start with a hash: #1
563    5. data from file access start with a percent: %filename(1)
564
565    After translation all parameter and qualifiers are replaced.
566
567    If the global option "Verify" is set the translated command string is saved
568    into "shx_translated". This string is rebuild from the translated parts,
569    so qualifiers may appear not in original order.
570
571    In order to test this class, we define a dummy command object.
572    >>> sw = Switches()
573    >>> sw.Verify = True
574    >>> cmd = {
575    ...    'shx_command': 'echo',
576    ...    'shx_converted': True,
577    ...    'shx_parameter': ['$PI', '$EXCLAMATION'],
578    ...    'shx_qualifiers': {'FOO': '$DOLLAR', 'BAR': True},
579    ... }
580    >>> _ = Translate(cmd, switches=sw)
581    >>> cmd['shx_translated']
582    'ECHO 3.1415926535897931 ! /FOO=$ /BAR'
583    """
584
585    system = {
586        "BLANK": " ",
587        "EXCLAMATION": "!",
588        "QUOTES": '"',
589        "DOLLAR": "$",
590        "PERCENT": "%",
591        "HAT": "^",
592        "BAR": "|",
593        "SLASH": "/",
594        "NUMBER": "#",
595        "PI": "3.1415926535897931",
596        "SH_ID": lambda: "SH_%i_" % os.getpid(),
597        "X": "$X", # really no idea for what this is good for...
598        "DSPTRCS": "_internalvars",
599        "TOTTRCS": "_internalvars",
600        "STATUS": "_internalvars",
601
602        # XXX todo
603        "SYSTIME": None,
604        "VERSION": None,
605        "DSP_X": None,
606        "DSP_Y": None,
607        "DSP_W": None,
608        "DSP_H": None,
609        "DSP_XMAX": None,
610        "DSP_YMAX": None,
611        "TITLESTYLE": None,
612        "TRCINFOSTYLE": None,
613        "ZEROTRCSTYLE": None,
614        "TIMEAXISSTYLE": None,
615        "MARKSTYLE": None,
616        "PMSTYLE": None,
617
618        # special treatment necessary
619        # syntax is HEXCHAR3B
620        "HEXCHAR": None,
621    }
622
623    def __init__(self, cmd=None, script=None, switches=None):
624        self.script = script
625
626        if switches is None:
627            switches = Switches()
628
629        self.switches = switches
630
631        # If no command structure is found, return silently.
632        if not cmd:
633            return
634
635        # translate parameters
636        for i, p in enumerate(cmd["shx_parameter"]):
637            cmd["shx_parameter"][i] = self._translate(p)
638
639        # translate qualifiers
640        for q in cmd["shx_qualifiers"]:
641            p = cmd["shx_qualifiers"][q]
642
643            # Skip qualifiers that act as switches.
644            if type(p) == bool:
645                continue
646
647            cmd["shx_qualifiers"][q] = self._translate(p)
648
649        # Actually this is only for debugging purposes.
650        if not switches.Verify:
651            return
652
653        qual = []
654        for q in cmd["shx_qualifiers"]:
655            if type(cmd["shx_qualifiers"][q]) == bool:
656                qual.append(q)
657            else:
658                qual.append("%s=%s" % (q, cmd["shx_qualifiers"][q]))
659
660        cmd["shx_translated"] = " ".join([
661            cmd["shx_converted"] and cmd["shx_command"].upper() or \
662                                                             cmd["shx_command"],
663            " ".join(cmd["shx_parameter"]),
664            len(qual) and "/" + " /".join(qual) or "",
665        ])
666
667    def _translate(self, value):
668        if not value:
669            return ''
670
671        idmap = {
672            '$': self.__handleSystem,
673            '"': self.__handleSymbol,
674            '^': self.__handleTrace,
675            '#': self.__handleOption,
676            '%': self.__handleFile,
677        }
678
679        id = value[0]
680
681        # concat operator
682        if id == "|":
683            parts = filter(None, value.split("|"))
684            newparts = [self._translate(i) for i in parts]
685            return "".join(newparts)
686
687        # nothing to translate
688        if id not in idmap.keys():
689            return value
690
691        value = value[1:]
692
693        try:
694            return idmap[id](value)
695        except NotImplementedError:
696            raise NotImplementedError
697        except:
698            raise ShxError("'%s' could not be translated!" % value)
699
700    def __handleSystem(self, name):
701        name = self._translate(name)
702        try:
703            x = self.system[name.upper()]
704        except KeyError:
705            if not name.upper()[:-2] == "HEXCHAR":
706                raise ShxError("System variable '%s' not found!" % name)
707            else:
708                try:
709                    x = chr(int(name[-2:], 16))
710                except:
711                    raise ShxError("Invalid hexadecimal value: %s" % name[-2:])
712
713        if x == None:
714            raise NotImplementedError
715
716        if callable(x):
717            return x()
718        else:
719            x = getattr(self, x, x)
720            if callable(x):
721                return x(name)
722            else:
723                return x
724
725    def __handleSymbol(self, name):
726        name = self._translate(name)
727        try:
728            return getattr(self.script.symbols, name)
729        except:
730            raise ShxError("Symbol '%s' not found!" % name)
731
732    def __handleTrace(self, name):
733        try:
734            name, traceno = name.split("(")
735        except:
736            traceno = "1"
737
738        name = self._translate(name)
739        traceno = int(self._translate(traceno.split(")")[0]))
740
741        if traceno < 1:
742            logMessage("warning.parse", "Trace numbering starts with '1'")
743            traceno = 1
744
745        try:
746            # internal counting starts from zero
747            trace = Traces[traceno - 1]
748        except:
749            logMessage("error.parse", "Trace number %u not present" % traceno)
750
751        try:
752            res = trace._shxInfo(name)
753            return res
754        except NameError:
755            logMessage("error.trace", "undefined info entry name '%s'" % name)
756
757    def __handleOption(self, name):
758        """
759        Return parameters set on procedure call.
760
761        Possible values:
762        #0 - filename
763        #1..#99 - parameter number xx
764        #name - qualifier "name"
765        """
766        if name.isdigit():
767            id = int(name)-1
768            if id >= 0:
769                return self.script.parameters['shx_parameter'][id]
770            else:
771                return self.script.filename
772        else:
773            name = self._translate(name)
774            return self.script.parameters['shx_qualifiers'][name]
775
776    def __handleFile(self, name):
777        # split line count (if any)
778        try:
779            f, lineno = name.split("(")
780        except ValueError:
781            lineno = "1"
782            f = name
783        else:
784            lineno = lineno[:-1]
785
786        f = self._translate(f)
787        lineno = int(self._translate(lineno))
788
789        # if file not found, try fallback
790        flist = [f, os.path.join(Settings.config.paths.globals[0], f)]
791
792        for i, f in enumerate(flist):
793            p, f = os.path.split(f)
794            try:
795                _ = f.index(".")
796            except ValueError:
797                f += ".STX"
798
799            flist[i] = os.path.join(p, f)
800
801        for f in flist:
802            try:
803                for cnt, line in enumerate(open(f, "r").readlines()):
804                    if cnt + 1 == lineno:
805                        return line.strip()
806            except IOError:
807                continue
808
809        if lineno == 0:
810            return str(cnt+1)
811        else:
812            raise ShxError("Line %d not found in file '%s'!" % (lineno, f))
813
814    @staticmethod
815    def _internalvars(name):
816        return Settings.Runtime[name.lower()]
817
818
819class Symbol(object):
820    """
821    This class holds locals symbols. Access to global symbols is granted.
822
823    Important note: All symbol names are converted into upper case!
824
825    >>> s = Symbol()
826    >>> s.foo = 1
827    >>> s.foo
828    1
829    >>> s.setGlobal("bar", "qux")
830    >>> s.bar
831    'qux'
832
833    Local symbols mask global ones! This can be undone by deleting the symbol.
834    >>> s.bar = 5
835    >>> s.bar
836    5
837    >>> del s.bar
838    >>> s.bar
839    'qux'
840
841    If no local symbol exists, the global one will be removed.
842    >>> del s.bar
843    >>> s.bar #doctest: +ELLIPSIS
844    Traceback (most recent call last):
845    ...
846    ShxError: Symbol 'BAR' not found!
847
848    If one wants to delete a global symbol but keep the local symbol:
849    >>> s.setGlobal("bar", "global")
850    >>> s.bar
851    'global'
852    >>> s.bar = "local"
853    >>> s.deleteGlobal("bar")
854    >>> s.bar
855    'local'
856    """
857
858    def __init__(self):
859        # this is a singleton
860        self.__dict__["_globals"] = Settings.Globals
861
862    def __getattr__(self, name):
863        """
864        This method is called only, if the requested name was not found in
865        __dict__
866        """
867        name = name.upper()
868
869        # local vars (but changed case)
870        try:
871            return self.__dict__[name]
872        except KeyError:
873            pass
874
875        # next try inside globals
876        try:
877            return self._globals[name]
878        except KeyError:
879            raise ShxError("Symbol '%s' not found!" % name)
880
881    def __setattr__(self, name, value):
882        self.__dict__[name.upper()] = value
883
884    def __delattr__(self, name):
885        name = name.upper()
886
887        # delete local symbol
888        try:
889            del self.__dict__[name]
890        except KeyError:
891            pass
892        else:
893            return
894
895        self.deleteGlobal(name)
896
897    def setGlobal(self, name, value=None):
898        self._globals[name.upper()] = value
899
900    def deleteGlobal(self, name):
901        try:
902            del self._globals[name.upper()]
903        except KeyError:
904            raise ShxError("Symbol %s not found!" % name)
905
906
907class Switches(object):
908    """
909    This class holds Seismic Handler's switches.
910
911    Support for global switches is present via runtime's attributes.
912
913    >>> sw = Switches()
914    >>> sw.keeptraces
915    False
916
917    Only certain switches are allowed:
918    >>> sw.test #doctest: +ELLIPSIS
919    Traceback (most recent call last):
920    ...
921    ShxError: no such switch: Test
922
923    It's not possible to create new switches:
924    >>> sw.bar = 1 #doctest: +ELLIPSIS
925    Traceback (most recent call last):
926    ...
927    ShxError: Unsupported switch: Bar
928
929    "on" and True indicate switch activation:
930    >>> sw.keeptraces = "on"
931    >>> sw.keeptraces
932    True
933
934    All other values will set switch to False:
935    >>> sw.capcnv = "foo"
936    >>> sw.capcnv
937    False
938    """
939
940    def __init__(self):
941        """
942        On init global switches are used by default.
943        """
944        self._switches = copy(Settings._Switches)
945
946    def __getattr__(self, name):
947        if name.startswith("_"):
948            return object.__getattribute__(self, name)
949        else:
950            name = name.capitalize()
951
952            try:
953                return object.__getattribute__(self, "_switches")[name]
954            except:
955                raise ShxError("no such switch: %s" % name)
956
957    def __setattr__(self, name, value):
958        """
959        Only certain switches are allowed.
960
961        "on" and True: True
962        other: False
963        """
964        name = name.capitalize()
965        if name.startswith("_"):
966            object.__setattr__(self, name, value)
967        elif name in self._switches.keys():
968            sw = self._switches
969            if value is True or (hasattr(value, "lower") and value.lower() == "on"):
970                sw[name] = True
971            else:
972                sw[name] = False
973        else:
974            raise ShxError("Unsupported switch: %s" % name)
975
976    def keys(self):
977        """
978        Simulate dict's keys method.
979        """
980        return self._switches.keys()
981
982
983def getVar(name, symbolset=None, switches=None):
984    """
985    Helper function to translate any variable.
986
987    name: variable to translate ($pi, "foo, ^length(1), ...
988    """
989
990    if not symbolset:
991        s = Translate(switches=switches)
992    else:
993        x = AttribDict()
994        x.symbols = symbolset
995        s = Translate(None, x, switches=switches)
996
997    return s._translate(name)
998
999
1000if __name__ == "__main__":
1001    import doctest
1002#    doctest.testmod(exclude_empty=True, verbose=True)
1003    doctest.testmod(exclude_empty=True)
Note: See TracBrowser for help on using the repository browser.