namespace/dictionary quandry
Peter Otten
__peter__ at web.de
Tue Sep 21 02:54:43 EDT 2004
Jack Carter wrote:
> Basically this is to go into a tool that accepts
> commandline arguments, one at a time, but will also
> allow scripting for testing purposes. Thus the desire
> to leverage off python for the commandline interface,
> but not for the whole program.
>
> The input from the user will be either from the console or
> read from a command file. A simple example of what could
> be entered is:
>
> bosco=5
> if 1:
> print bosco
> attach bosco 9
> bosco=7
> print bosco
> attach bosco 9
>
> The result I would expect would be for my DoAttach()
> routine to receive the python evaluated value of each
> of the arguments leaving the ones that it doesn't understand
> alone. Whether this happens automagically or by hand I don't
> care as long as I get what right value.
>
> In the above trying to follow your advice, remembering that
> at this stage of the game I am probably missing the point, this
> is the result I expect:
>
> johmar % demo.py
>>>> bosco=5
>>>> if 1:
> ... print bosco
> ... attach bosco 9
> ... bosco=7
> ... print bosco
> ... attach bosco 9
> ...
> 5
> DoAttach: [5, 9]
> 7
> DoAttach: [7, 9]
>
> This is what I get:
>
> johmar % demo.py
>>>> bosco=5
>>>> if 1:
> ... print bosco
> ... attach bosco 9
> ... bosco=7
> ... print bosco
> ... attach bosco 9
> ...
> 5
> DoAttach: ['bosco', '9']
> 7
> DoAttach: ['bosco', '9']
>
> Now I realize that this is probably due to the fact that
> I have the lines:
>
> if white_spaces:
> line = front_padding + "myparse." + function + "(" +
> str(args) + ")"
> else :
> line = "myparse." + function + "(" + str(args) + ")"
>
> which put make the arguments strings, but that is because I don't
> know how to appropriately pack the "line" for later parsing. Maybe
> that is the crux of my problem. Remember, there will be many commands
> for my tool and the arguments will be variable length and this code
> will not know what variables the gentle use would use.
>
> Here is the simplified code with hopefully the tabs expanded
> base on your earlier input. Hopefully you'll see the obvious
> error of my way and point it out.
>
> Thanks ever so much,
>
> Jack
>
> **********************************
> demo.py
> **********************************
> #!/usr/bin/env python
>
> import myparse
>
> cli = myparse.CLI(globals())
> cli.interact()
>
> **********************************
> myparse.py
> **********************************
> import code
> import re
> import string
> import sys
>
>
################################################################################
> #
> # DoAttach
> #
> # Dummy function that I will eventually use to do
> # real stuff.
> #
>
################################################################################
> def DoAttach(args):
>
> print "DoAttach:", args
> pass
>
>
> class CLI(code.InteractiveConsole):
> """Simple test of a Python interpreter augmented with custom
> commands."""
>
> commands = { \
> "attach" : "DoAttach"
> }
>
> def __init__(self, locals = None):
>
> # Call super-class initializer
> code.InteractiveConsole.__init__(self, locals, "<console>")
>
> # Compile regular expression for finding commmands
> self.regexp = re.compile('[a-z]*')
>
>
> ##################################################################
> #
> # interact
> #
> # This will read and process input lines from within
> # my main application as though on a python commandline.
> #
> ##################################################################
> def interact(self):
>
> # Set the primary and secondary prompts
> sys.ps1 = ">>> "
> sys.ps2 = "... "
>
> # Input Loop
> is_more = 0
> bosco = 0
> while 1:
> try :
> # Display the appropriate prompt
> if not sys.stdin.isatty():
> prompt = ""
> elif is_more:
> prompt = sys.ps2
> else:
> prompt = sys.ps1
>
> # Read the next line of input
> #self.write("interact 1\n")
> line = self.raw_input(prompt)
>
> # TODO: add logging of input line here...
>
> # Process complete lines
> if 1:
> line = self.process(line)
>
> # Push incomplete lines onto input stack
> if line or is_more:
> is_more = self.push(line)
>
> # Handle CTRL-C
> except KeyboardInterrupt:
> self.write("\nKeyboardInterrupt\n")
> is_more = 0
> self.resetbuffer()
>
> # Handle CTRL-D
> except EOFError:
> self.write("\n")
> is_more = 0
> self.resetbuffer()
> raise SystemExit
>
> ##################################################################
> #
> # process
> #
> # This will determine if the input command is either
> # from my application's command language or a python
> # construct.
> #
> ##################################################################
> def process(parent, line):
>
> # Attempt to match line against our command regular expression
>
> temp_line = string.lstrip(line)
> len_1 = len(line)
> len_2 = len(temp_line)
>
> white_spaces = len_1-len_2
> if white_spaces:
> front_padding = line[0:white_spaces]
>
> match = parent.regexp.match(temp_line)
> if match is not None:
>
> #parent.write("process 1\n")
> # Extract the command and argument strings
> cmd_string = match.group()
> arg_string = string.lstrip(temp_line[match.end():])
>
> # Find the function for this command in the command dictionary
> function = parent.commands.get(cmd_string)
>
> if function is not None:
>
> # Split argument string into individual arguments
> args = string.split(arg_string)
>
> # Convert to Python function-call syntax for this command
> if white_spaces:
> line = front_padding + "myparse." + function + "(" +
> str(args) + ")"
> else :
> line = "myparse." + function + "(" + str(args) + ")"
>
# let's add some feedback
print "fed to the snake:", line
> # Return the line to be processed by Python
> return line
Now
>>> bosco = 1
fed to the snake: bosco = 1
>>> attach bosco 2
fed to the snake: myparse.DoAttach(['bosco', '2'])
DoAttach: ['bosco', '2']
>>>
You build a function call
myparse.DoAttach(['bosco', '2'])
But what you need would rather be
myparse.DoAttach([bosco, 2])
To achieve that you have to somehow extract (Python-compatible) expressions
for the arguments given in the line
attach bosco 2
which can be arbitrarily complex depending on how you defined your custom
language. Assumming that you use the simplest possible spec, a
space-separated list of already Python-compatible expressions that gives
you
def parseArgs(args):
return args.split()
def makePythonCall(func, args):
return "%s([%s])" % (func, ", ".join(args))
and the process() method will become:
def process(parent, line):
# Attempt to match line against our command regular expression
temp_line = string.lstrip(line)
len_1 = len(line)
len_2 = len(temp_line)
white_spaces = len_1-len_2
if white_spaces:
front_padding = line[0:white_spaces]
match = parent.regexp.match(temp_line)
if match is not None:
#parent.write("process 1\n")
# Extract the command and argument strings
cmd_string = match.group()
arg_string = string.lstrip(temp_line[match.end():])
# Find the function for this command in the command dictionary
function = parent.commands.get(cmd_string)
if function is not None:
args = parseArgs(arg_string)
line = makePythonCall("myparse." + function, args)
if white_spaces:
line = front_padding + line
print "fed to the snake:", line
# Return the line to be processed by Python
return line
Testing it:
>>> bosco = 1
fed to the snake: bosco = 1
>>> attach bosco 2
fed to the snake: myparse.DoAttach([bosco, 2])
DoAttach: [1, 2]
>>> if 1:
fed to the snake: if 1:
... attach bosco
fed to the snake: myparse.DoAttach([bosco])
... bosco = 3
fed to the snake: bosco = 3
... attach bosco
fed to the snake: myparse.DoAttach([bosco])
...
fed to the snake:
DoAttach: [1]
DoAttach: [3]
Works here, but is not very robust:
>>> attach = 99
fed to the snake: myparse.DoAttach([=, 99])
File "<console>", line 1
myparse.DoAttach([=, 99])
^
SyntaxError: invalid syntax
>>> attach(bosco)
fed to the snake: myparse.DoAttach([(bosco)])
DoAttach: [3]
>>> attach bosco "so what"
fed to the snake: myparse.DoAttach([bosco, "so, what"])
DoAttach: [3, 'so, what'] # note the comma
Peter
More information about the Python-list
mailing list