From b5809a186ede6d365631e5e93d8d31bdf7f19f55 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Fri, 18 Dec 2015 13:17:17 +0100 Subject: [PATCH 01/18] [*] handle Calls in func_name2dotted_exec(). more cases to come, for sure. --- ayrton/castt.py | 7 +++++-- ayrton/tests/test_castt.py | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ayrton/castt.py b/ayrton/castt.py index 7bfeac9..01826b1 100644 --- a/ayrton/castt.py +++ b/ayrton/castt.py @@ -74,8 +74,11 @@ def func_name2dotted_exec (node): complete_name= str (pprint (node)) - while type (node) in (Attribute, Subscript): - node= node.value + while type (node) in (Attribute, Subscript, Call): + if type (node) in (Attribute, Subscript): + node= node.value + elif type (node) in (Call, ): + node= node.func return (node.id, complete_name) diff --git a/ayrton/tests/test_castt.py b/ayrton/tests/test_castt.py index 0012d76..c2a3fac 100644 --- a/ayrton/tests/test_castt.py +++ b/ayrton/tests/test_castt.py @@ -200,6 +200,13 @@ def testDottedSubscriptComplex (self): # this is a very strange but possible executable name self.assertEqual (combined, 'argv[3].split[:54]') + def testDottedCall (self): + single, combined= castt.func_name2dotted_exec (parse_expression ('str (foo).strip ()')) + + self.assertEqual (single, 'str') + # this is a very strange but possible executable name + self.assertEqual (combined, 'str (foo, ).strip ()') + class TestWeirdErrors (unittest.TestCase): check_attrs= check_attrs From 23e5ccbdc1fcbeaaef0a6c4b86d472e632dfdb5b Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Tue, 19 Jan 2016 11:48:37 +0100 Subject: [PATCH 02/18] [*] make compilation and run two different lines so we can prvide slightly better error report. --- ayrton/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ayrton/__init__.py b/ayrton/__init__.py index 2d73f56..0863bb3 100644 --- a/ayrton/__init__.py +++ b/ayrton/__init__.py @@ -177,7 +177,8 @@ def run_tree (self, tree, file_name, argv=None): logger.debug2 ('AST: %s', ast.dump (tree)) logger.debug2 ('code: \n%s', pprint (tree)) - return self.run_code (compile (tree, file_name, 'exec'), file_name, argv) + code= compile (tree, file_name, 'exec') + return self.run_code (code, file_name, argv) def run_code (self, code, file_name, argv=None): From d3220b31485c87bfdc313e9d812db038d3d53a7d Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 3 Feb 2016 12:50:57 +0100 Subject: [PATCH 03/18] [*] expand iterables in place on Command.__call__(). --- ayrton/execute.py | 29 +++++++++++++++++++++++------ ayrton/tests/test_execute.py | 24 ++++++++++++++++++++++++ doc/source/reference.rst | 11 +++++++++-- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/ayrton/execute.py b/ayrton/execute.py index 2e61309..f92ff3a 100644 --- a/ayrton/execute.py +++ b/ayrton/execute.py @@ -94,6 +94,12 @@ def resolve_program(program): return path +def isiterable (o): + """Returns True if o is iterable but not str/bytes type.""" + # TODO: what about Mappings? + return ( isinstance (o, Iterable) and + not isinstance (o, (bytes, str)) ) + class Command: default_options= dict ( _in_tty= False, @@ -342,7 +348,12 @@ def prepare_args (self, cmd, args, kwargs): if isinstance (arg, o): self.prepare_arg (ans, arg.key, arg.value) else: - ans.append (str (arg)) + if isiterable (arg): + # a sequence type that is not string like + for elem in arg: + ans.append (str (elem)) + else: + ans.append (str (arg)) for k, v in kwargs.items (): self.prepare_arg (ans, k, v) @@ -351,12 +362,18 @@ def prepare_args (self, cmd, args, kwargs): def prepare_arg (self, seq, name, value): if value!=False: - seq.append (name) + if isiterable (value): + for elem in value: + seq.append (name) + seq.append (str (elem)) + + else: + seq.append (name) - # this is not the same as 'not value' - # because value can have any, well, value of any kind - if value!=True: - seq.append (str (value)) + # this is not the same as 'not value' + # because value can have any, well, value of any kind + if value!=True: + seq.append (str (value)) else: # TODO: --no-option? pass diff --git a/ayrton/tests/test_execute.py b/ayrton/tests/test_execute.py index 4e78763..bbdc7c3 100644 --- a/ayrton/tests/test_execute.py +++ b/ayrton/tests/test_execute.py @@ -292,3 +292,27 @@ def foo (self): # runner.options['errexit']= True pass + +class HelperFunctions (unittest.TestCase): + def setUp (self): + self.c= Command ('/bin/true') + + def testPrepareArgsSimple (self): + self.assertEqual (self.c.prepare_args ('foo', [], {}), + [ 'foo' ]) + + def testPrepareArgsSingleArg (self): + self.assertEqual (self.c.prepare_args ('foo', ['bar'], {}), + [ 'foo', 'bar' ]) + + def testPrepareArgsSingleKwarg (self): + self.assertEqual (self.c.prepare_args ('foo', [], { 'bar': 'quux' }), + [ 'foo', 'bar', 'quux' ]) + + def testPrepareArgsIterableArg (self): + self.assertEqual (self.c.prepare_args ('foo', [ [ 1, 2 ] ], {}), + [ 'foo', '1', '2' ]) + + def testPrepareArgsIterableKwarg (self): + self.assertEqual (self.c.prepare_args ('foo', [], { 'bar': [ 1, 2 ]}), + [ 'foo', 'bar', '1', 'bar', '2' ]) diff --git a/doc/source/reference.rst b/doc/source/reference.rst index b8743aa..839fa89 100644 --- a/doc/source/reference.rst +++ b/doc/source/reference.rst @@ -106,10 +106,17 @@ Functions Executes the binary *foo*, searching the binary using :py:data:`path`. Arguments are used as positional arguments for the command, except for the special keyword arguments listed below. This - returns a :py:class:`Command`. The syntaxis for Commands departs a little from + returns a :py:class:`Command`. + + The syntaxis for Commands departs a little from pure Python. Python expressions are allowed as keyword names, so `-o` and `--long-option` are valid. Also, keywords and positional arguments can be mixed, - as in `find (-L=True, '/', -name='*.so')` + as in `find (-L=True, '/', -name='*.so')`. + + Iterable arguments that are not + :py:class:`str` or :py:class:`bytes` are expanded in situ, so `foo(..., i, ...)` + is expanded to `foo (..., i[0], i[1], ...` and `foo(..., k=i, ...)` is expanded + to `foo (..., k=i[0], k=i[1], ...`. .. py:attribute:: _in From 413a08140e6cfec587bb5c667507349753a5cadb Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 3 Feb 2016 14:50:26 +0100 Subject: [PATCH 04/18] [*] sometimes some builtins are not defined (in ipython3, for instance, exit). --- ayrton/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ayrton/__init__.py b/ayrton/__init__.py index 0863bb3..f6dfde5 100644 --- a/ayrton/__init__.py +++ b/ayrton/__init__.py @@ -67,7 +67,8 @@ def polute (self): # weed out some stuff for weed in ('copyright', '__doc__', 'help', '__package__', 'credits', 'license', '__name__', 'quit', 'exit'): - del self[weed] + if weed in self: + del self[weed] # these functions will be loaded from each module and put in the globals # tuples (src, dst) renames function src to dst From 47afd064b3646ffc6bcf65ba99f0cb0a71e6cecb Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 3 Feb 2016 16:17:47 +0100 Subject: [PATCH 05/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 347b55f..3c06524 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ If you're unsure on how this apply to your interpreted programs, check Currently `ayrton` is under heavy development, so if you're following it and clone it, use the branch `develop`. -# Instalation +# Installation `ayrton` depends on two pieces of code. Python is the most obvious; it has been developed in its version 3.3. Python 3.2 is not enough, sorry. On the other hand, From 313ff060bd98b1c9dda91789023363f890054df6 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 3 Feb 2016 17:16:33 +0100 Subject: [PATCH 06/18] [+] tracer for printing lines as they're executed. --- ayrton/__init__.py | 55 ++++++++++++++++++++++++++++++++++++---------- bin/ayrton | 13 ++++++++--- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/ayrton/__init__.py b/ayrton/__init__.py index f6dfde5..fa54963 100644 --- a/ayrton/__init__.py +++ b/ayrton/__init__.py @@ -24,6 +24,7 @@ import logging import dis import traceback +import linecache # patch logging so we have debug2 and debug3 import ayrton.utils @@ -146,6 +147,8 @@ def __init__ (self, g=None, l=None, **kwargs): self.options= {} self.pending_children= [] + self.file_name= None + self.script= None # HACK to update the singleton # this might break if we implement subinstances @@ -153,7 +156,7 @@ def __init__ (self, g=None, l=None, **kwargs): runner= self - def run_file (self, file_name, argv=None): + def run_file (self, file_name, argv=None, trace=False): # it's a pity that parse() does not accept a file as input # so we could avoid reading the whole file logger.debug ('running from file %s', file_name) @@ -162,27 +165,30 @@ def run_file (self, file_name, argv=None): script= f.read () f.close () - return self.run_script (script, file_name, argv) + return self.run_script (script, file_name, argv, trace=trace) - def run_script (self, script, file_name, argv=None): + def run_script (self, script, file_name, argv=None, trace=False): logger.debug ('running script:\n-----------\n%s\n-----------', script) + self.file_name= file_name + self.script= script.split ('\n') + tree= parse (script, file_name) # TODO: self.locals? tree= CrazyASTTransformer (self.globals, file_name).modify (tree) - return self.run_tree (tree, file_name, argv) + return self.run_tree (tree, file_name, argv, trace=trace) - def run_tree (self, tree, file_name, argv=None): + def run_tree (self, tree, file_name, argv=None, trace=False): logger.debug2 ('AST: %s', ast.dump (tree)) logger.debug2 ('code: \n%s', pprint (tree)) code= compile (tree, file_name, 'exec') - return self.run_code (code, file_name, argv) + return self.run_code (code, file_name, argv, trace=trace) - def run_code (self, code, file_name, argv=None): + def run_code (self, code, file_name, argv=None, trace=False): if logger.parent.level<=logging.DEBUG2: logger.debug2 ('------------------') logger.debug2 ('main (gobal) code:') @@ -240,11 +246,15 @@ def run_code (self, code, file_name, argv=None): error= None try: logger.debug3 ('globals for script: %s', ayrton.utils.dump_dict (self.globals)) + if trace: + sys.settrace (self.global_tracer) exec (code, self.globals, self.locals) except Exception as e: logger.debug ('script finished by Exception') logger.debug (traceback.format_exc ()) error= e + finally: + sys.settrace (None) logger.debug3 ('globals at script exit: %s', ayrton.utils.dump_dict (self.globals)) logger.debug3 ('locals at script exit: %s', ayrton.utils.dump_dict (self.locals)) @@ -263,19 +273,42 @@ def wait_for_pending_children (self): child.wait () + def global_tracer (self, frame, event, arg): + """""" + logger.debug2 ('global_tracer: %s', event) + if event in ('call', 'line'): + return self.local_tracer + else: + return None + + + def local_tracer (self, frame, event, arg): + if event=='line': + filename= frame.f_code.co_filename + if filename==self.file_name: + lineno= frame.f_lineno + + line= linecache.getline (filename, lineno) + if line=='': + line= self.script[lineno-1] # line numbers start at 1 + + logger.debug2 ('trace e: %s, f: %s, n: %d, l: %s', event, filename, lineno, line) + print ("+ %s" % line, end='', file=sys.stderr) # line already has a \n + + def run_tree (tree, g, l): """main entry point for remote()""" runner= Ayrton (g=g, l=l) return runner.run_tree (tree, 'unknown_tree') -def run_file_or_script (script=None, file_name='script_from_command_line', argv=None, - **kwargs): +def run_file_or_script (script=None, file_name='script_from_command_line', + argv=None, trace=False, **kwargs): """Main entry point for bin/ayrton and unittests.""" runner= Ayrton (**kwargs) if script is None: - v= runner.run_file (file_name, argv) + v= runner.run_file (file_name, argv, trace=trace) else: - v= runner.run_script (script, file_name, argv) + v= runner.run_script (script, file_name, argv, trace=trace) return v diff --git a/bin/ayrton b/bin/ayrton index fc29ecb..0865c69 100755 --- a/bin/ayrton +++ b/bin/ayrton @@ -32,8 +32,8 @@ Usage: ayrton [-h|--help] ayrton [-v|--version] - ayrton [-c|--script] SCRIPT [argv ...] - ayrton [-f|--file] file [argv ...] + ayrton [-x|--trace] -c|--script SCRIPT [argv ...] + ayrton [-x|--trace] [-f|--file] file [argv ...] Options: -h, --help show this help message and exit @@ -42,6 +42,7 @@ Options: argument. -v, --version show program's version number and exit [-f, --file] FILE Name of the file from which the script is read. + -x, --trace Trace execution Arguments: argv Arguments to be passed to the script.""" @@ -50,8 +51,10 @@ Arguments: # it *is* kinda idiotic, but I can't tell any of the parsing modules to just parse those options # and the options are not many and are simple to handle, anyways +file_name= None script= None file= None +trace= False # sys.argv[0] is 'ayrton' args= sys.argv[1:] reason= None @@ -79,6 +82,10 @@ try: # stop parsing, anything else is passed to the script break + if arg in ('-x', '--trace'): + trace= True + continue + if arg in ('-f', '--file'): reason= 'Missing argument to option %s' % arg # -f|--file is optional (because idiot /usr/bin/env ayrton -f does not work) @@ -105,7 +112,7 @@ except IndexError: try: v= ayrton.run_file_or_script (file_name=file_name, script=script, - argv=script_args) + argv=script_args, trace=trace) if type(v)==bool: if v: From ebcb53b3c083506248c436e6e1dcc10df57b7872 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 3 Feb 2016 17:17:27 +0100 Subject: [PATCH 07/18] [*] fix typo. --- bin/ayrton | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/ayrton b/bin/ayrton index 0865c69..5855195 100755 --- a/bin/ayrton +++ b/bin/ayrton @@ -108,7 +108,7 @@ try: except IndexError: print (reason) print (usage) - sys,exit (1) + sys.exit (1) try: v= ayrton.run_file_or_script (file_name=file_name, script=script, From 74abd2f3dacba22e2b41de02f18b306da083fa2e Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 3 Feb 2016 17:17:54 +0100 Subject: [PATCH 08/18] [*] print usage when no script or file was given. --- bin/ayrton | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/ayrton b/bin/ayrton index 5855195..997bec4 100755 --- a/bin/ayrton +++ b/bin/ayrton @@ -110,6 +110,10 @@ except IndexError: print (usage) sys.exit (1) +if file_name is None and script is None: + print (usage) + sys.exit (1) + try: v= ayrton.run_file_or_script (file_name=file_name, script=script, argv=script_args, trace=trace) From 6b23a23a8f0b11b10cfd0a347e55de8ff1500976 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 3 Feb 2016 17:18:12 +0100 Subject: [PATCH 09/18] [*] fixes in the usage text. --- bin/ayrton | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/ayrton b/bin/ayrton index 997bec4..3ded9a9 100755 --- a/bin/ayrton +++ b/bin/ayrton @@ -30,17 +30,17 @@ usage="""ayrton - A Python-based shell-like scripting language. Usage: - ayrton [-h|--help] - ayrton [-v|--version] + ayrton -h|--help + ayrton -v|--version ayrton [-x|--trace] -c|--script SCRIPT [argv ...] ayrton [-x|--trace] [-f|--file] file [argv ...] Options: - -h, --help show this help message and exit + -h, --help Show this help message and exit -c SCRIPT, --script SCRIPT If this option is present, the script is read from its argument. - -v, --version show program's version number and exit + -v, --version Show program's version number and exit [-f, --file] FILE Name of the file from which the script is read. -x, --trace Trace execution From ce5379a11b9a3fbf1c5088833b68607a1cb9f0a1 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Fri, 5 Feb 2016 11:39:31 +0100 Subject: [PATCH 10/18] [*] preparing the terrain for more verbose tracing modes. --- ayrton/__init__.py | 43 ++++++++++++++++++++++++++++++++----------- bin/ayrton | 6 +++--- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/ayrton/__init__.py b/ayrton/__init__.py index fa54963..8b84e54 100644 --- a/ayrton/__init__.py +++ b/ayrton/__init__.py @@ -50,6 +50,14 @@ __version__= '0.7' +class ExecParams: + def __init__ (self, **kwargs): + # defaults + self.trace= False + + self.__dict__.update (kwargs) + + def parse (script, file_name=''): parser= PythonParser (None) info= CompileInfo (file_name, 'exec') @@ -149,6 +157,7 @@ def __init__ (self, g=None, l=None, **kwargs): self.pending_children= [] self.file_name= None self.script= None + self.params= ExecParams () # HACK to update the singleton # this might break if we implement subinstances @@ -156,39 +165,47 @@ def __init__ (self, g=None, l=None, **kwargs): runner= self - def run_file (self, file_name, argv=None, trace=False): + def run_file (self, file_name, argv=None, params=None): # it's a pity that parse() does not accept a file as input # so we could avoid reading the whole file + # and now we read it anyways in the case of tracing logger.debug ('running from file %s', file_name) f= open (file_name) script= f.read () f.close () - return self.run_script (script, file_name, argv, trace=trace) + return self.run_script (script, file_name, argv, params) - def run_script (self, script, file_name, argv=None, trace=False): + def run_script (self, script, file_name, argv=None, params=None): logger.debug ('running script:\n-----------\n%s\n-----------', script) self.file_name= file_name self.script= script.split ('\n') + # up to this point the script is the whole script in one string + # because parse() needs it that way tree= parse (script, file_name) # TODO: self.locals? tree= CrazyASTTransformer (self.globals, file_name).modify (tree) - return self.run_tree (tree, file_name, argv, trace=trace) + return self.run_tree (tree, file_name, argv, params) - def run_tree (self, tree, file_name, argv=None, trace=False): + def run_tree (self, tree, file_name, argv=None, params=None): logger.debug2 ('AST: %s', ast.dump (tree)) logger.debug2 ('code: \n%s', pprint (tree)) + if params is not None: + # we delay this assignment down to here because run_file(), + # run_script() and run_tree() are entry points + self.params= params + code= compile (tree, file_name, 'exec') - return self.run_code (code, file_name, argv, trace=trace) + return self.run_code (code, file_name, argv) - def run_code (self, code, file_name, argv=None, trace=False): + def run_code (self, code, file_name, argv=None): if logger.parent.level<=logging.DEBUG2: logger.debug2 ('------------------') logger.debug2 ('main (gobal) code:') @@ -246,7 +263,7 @@ def run_code (self, code, file_name, argv=None, trace=False): error= None try: logger.debug3 ('globals for script: %s', ayrton.utils.dump_dict (self.globals)) - if trace: + if self.params.trace: sys.settrace (self.global_tracer) exec (code, self.globals, self.locals) except Exception as e: @@ -302,13 +319,17 @@ def run_tree (tree, g, l): return runner.run_tree (tree, 'unknown_tree') def run_file_or_script (script=None, file_name='script_from_command_line', - argv=None, trace=False, **kwargs): + argv=None, params=None, **kwargs): """Main entry point for bin/ayrton and unittests.""" runner= Ayrton (**kwargs) + + if params is None: + params= ExecParams () + if script is None: - v= runner.run_file (file_name, argv, trace=trace) + v= runner.run_file (file_name, argv, params) else: - v= runner.run_script (script, file_name, argv, trace=trace) + v= runner.run_script (script, file_name, argv, params) return v diff --git a/bin/ayrton b/bin/ayrton index 3ded9a9..ad87bf8 100755 --- a/bin/ayrton +++ b/bin/ayrton @@ -54,7 +54,7 @@ Arguments: file_name= None script= None file= None -trace= False +params= ayrton.ExecParams () # sys.argv[0] is 'ayrton' args= sys.argv[1:] reason= None @@ -83,7 +83,7 @@ try: break if arg in ('-x', '--trace'): - trace= True + params.trace= True continue if arg in ('-f', '--file'): @@ -116,7 +116,7 @@ if file_name is None and script is None: try: v= ayrton.run_file_or_script (file_name=file_name, script=script, - argv=script_args, trace=trace) + argv=script_args, params=params) if type(v)==bool: if v: From ad3083eb334e1402d219901de52cf9fcd860b190 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Fri, 5 Feb 2016 11:40:05 +0100 Subject: [PATCH 11/18] [*] s/script/file_name/, as that's what it is. --- ayrton/tests/test_ayrton.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ayrton/tests/test_ayrton.py b/ayrton/tests/test_ayrton.py index b7ace1e..e66970d 100644 --- a/ayrton/tests/test_ayrton.py +++ b/ayrton/tests/test_ayrton.py @@ -112,12 +112,15 @@ def setUp (self): self.runner= ayrton.Ayrton () - def doTest (self, script, expected=None, argv=None): + def doTest (self, file_name, expected=None, argv=None): if argv is not None: - argv.insert (0, script) + argv.insert (0, file_name) + else: + argv= [ file_name ] logger.debug (argv) - result= self.runner.run_file (os.path.join ('ayrton/tests/scripts', script), argv=argv) + result= self.runner.run_file (os.path.join ('ayrton/tests/scripts', file_name), + argv=argv) if expected is not None: self.assertEqual (result, expected) From e8d1785c7bf28a44e1f13e7990cb56e0823a8384 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Fri, 5 Feb 2016 16:38:45 +0100 Subject: [PATCH 12/18] [*] fixed bug in pypy's astbuilder, which uses py2's integer division ('/') instead of py3's ('//'). --- ayrton/parser/astcompiler/astbuilder.py | 5 ++++- ayrton/tests/test_pyparser.py | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ayrton/parser/astcompiler/astbuilder.py b/ayrton/parser/astcompiler/astbuilder.py index 1e077ea..9aeaf8e 100644 --- a/ayrton/parser/astcompiler/astbuilder.py +++ b/ayrton/parser/astcompiler/astbuilder.py @@ -345,6 +345,7 @@ def handle_suite(self, suite_node): def handle_if_stmt(self, if_node): child_count = len(if_node.children) if child_count == 4: + # simple if-then test = self.handle_expr(if_node.children[1]) suite = self.handle_suite(if_node.children[3]) new_node = ast.If (test, suite, []) @@ -353,6 +354,7 @@ def handle_if_stmt(self, if_node): return new_node otherwise_string = if_node.children[4].value if otherwise_string == "else": + # if-then-else test = self.handle_expr(if_node.children[1]) suite = self.handle_suite(if_node.children[3]) else_suite = self.handle_suite(if_node.children[6]) @@ -361,6 +363,7 @@ def handle_if_stmt(self, if_node): new_node.col_offset = if_node.col_offset return new_node elif otherwise_string == "elif": + # if-then-elif... elif_count = child_count - 4 after_elif = if_node.children[elif_count + 1] if after_elif.type == tokens.NAME and \ @@ -369,7 +372,7 @@ def handle_if_stmt(self, if_node): elif_count -= 3 else: has_else = False - elif_count /= 4 + elif_count //= 4 if has_else: last_elif = if_node.children[-6] last_elif_test = self.handle_expr(last_elif) diff --git a/ayrton/tests/test_pyparser.py b/ayrton/tests/test_pyparser.py index 05def0a..72d276d 100644 --- a/ayrton/tests/test_pyparser.py +++ b/ayrton/tests/test_pyparser.py @@ -84,3 +84,12 @@ def test_comp (self): def test_comp_if (self): self.parse ('[ x for x in foo() if x ]') + + + def test_elif (self): + self.parse ('''if True: + pass +elif False: + pass +else: + pass''') From 2bac12795115f04c25c6a713f744847d2f3f27ed Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Fri, 5 Feb 2016 17:13:42 +0100 Subject: [PATCH 13/18] [+] -xx|--trace-with-linenos to add line numbers to tracing. --- ayrton/__init__.py | 17 +++++++++++------ bin/ayrton | 20 +++++++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/ayrton/__init__.py b/ayrton/__init__.py index 8b84e54..e46593c 100644 --- a/ayrton/__init__.py +++ b/ayrton/__init__.py @@ -54,6 +54,7 @@ class ExecParams: def __init__ (self, **kwargs): # defaults self.trace= False + self.linenos= False self.__dict__.update (kwargs) @@ -265,6 +266,7 @@ def run_code (self, code, file_name, argv=None): logger.debug3 ('globals for script: %s', ayrton.utils.dump_dict (self.globals)) if self.params.trace: sys.settrace (self.global_tracer) + exec (code, self.globals, self.locals) except Exception as e: logger.debug ('script finished by Exception') @@ -301,16 +303,19 @@ def global_tracer (self, frame, event, arg): def local_tracer (self, frame, event, arg): if event=='line': - filename= frame.f_code.co_filename - if filename==self.file_name: + file_name= frame.f_code.co_filename + if self.params.trace_all or file_name==self.file_name: lineno= frame.f_lineno - line= linecache.getline (filename, lineno) + line= linecache.getline (file_name, lineno).rstrip () if line=='': - line= self.script[lineno-1] # line numbers start at 1 + line= self.script[lineno-1].rstrip () # line numbers start at 1 - logger.debug2 ('trace e: %s, f: %s, n: %d, l: %s', event, filename, lineno, line) - print ("+ %s" % line, end='', file=sys.stderr) # line already has a \n + logger.debug2 ('trace e: %s, f: %s, n: %d, l: %s', event, file_name, lineno, line) + if self.params.linenos: + print ("+ [%6d] %s" % (lineno, line), file=sys.stderr) + else: + print ("+ %s" % line, file=sys.stderr) def run_tree (tree, g, l): diff --git a/bin/ayrton b/bin/ayrton index ad87bf8..7c54278 100755 --- a/bin/ayrton +++ b/bin/ayrton @@ -32,17 +32,22 @@ Usage: ayrton -h|--help ayrton -v|--version - ayrton [-x|--trace] -c|--script SCRIPT [argv ...] - ayrton [-x|--trace] [-f|--file] file [argv ...] + ayrton [TRACE_OPTS] -c|--script SCRIPT [argv ...] + ayrton [TRACE_OPTS] [-f|--file] file [argv ...] Options: - -h, --help Show this help message and exit + -h, --help Show this help message and exit. -c SCRIPT, --script SCRIPT If this option is present, the script is read from its argument. - -v, --version Show program's version number and exit + -v, --version Show program's version number and exit. [-f, --file] FILE Name of the file from which the script is read. - -x, --trace Trace execution + +Trace Options: + -x, --trace Trace script execution. It does not trace other modules + used. + -xx, --trace-with-lineno + Trace and print the line number. Arguments: argv Arguments to be passed to the script.""" @@ -86,6 +91,11 @@ try: params.trace= True continue + if arg in ('-xx', '--trace-with-linenos'): + params.trace= True + params.linenos= True + continue + if arg in ('-f', '--file'): reason= 'Missing argument to option %s' % arg # -f|--file is optional (because idiot /usr/bin/env ayrton -f does not work) From ad3aafef6e0b971228e7134172a21fac3f1ad418 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Sat, 6 Feb 2016 10:50:20 +0100 Subject: [PATCH 14/18] [*] changed the parragraph about xonsh, fixes #20. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c06524..a41d3ae 100644 --- a/README.md +++ b/README.md @@ -217,14 +217,15 @@ A: (Short version) We think nobody provides all of `ayrton`'s features. A: ... [`sh`](https://amoffat.github.io/sh/)? Well, we started with `sh` as a basis of `ayrton`, but its buffered output didn't allow us to run editors and other TIU's. -A: ... [`xonsh`](http://xonsh.org/)? `xonsh` keeps environment variables well -separated from Python ones; it even has a Python mode and a 'subprocess' mode; +A: ... [`xonsh`](http://xonsh.org/)? `xonsh` keeps environment variables in a different +namespace than the Python ones; it even has a Python mode and a 'subprocess' mode +(although lots of Python characteristics can be used in the subprocess mode and vice versa); and is more oriented to being a shell. `ayrton` aims directly in the opposite direction. A: ... [`plumbum`](https://plumbum.readthedocs.org/en/latest/)? You could say that we independently thought of its piping and redirection syntax (but in reality we just -based ours on `bash`'s). Still, the fact that you fisrt build pipes and then execute +based ours on `bash`'s). Still, the fact that you first build pipes and then execute them looks weird for a SysAdmin. A: ... [`fabric`](http://www.fabfile.org/)? `fabric` is the only one that has remote From b6cb650b11b79490619968c0048606f282ad1275 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 10 Feb 2016 10:02:45 +0100 Subject: [PATCH 15/18] [*] doc fix. --- doc/source/reference.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/reference.rst b/doc/source/reference.rst index 839fa89..7939233 100644 --- a/doc/source/reference.rst +++ b/doc/source/reference.rst @@ -16,8 +16,8 @@ from shell like languages. This variable holds a list of strings, each one representing a directory. When binaries are executed (see :py:func:`foo`), they're searched in these - directories by appending *foo* to each of the directory until an executable - is found. + directories by appending *foo* to each one of the directories until an + executable is found. Exceptions ---------- From 6a9380c9f80bec5d1584b546a60252d3849b51e1 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 10 Feb 2016 10:09:04 +0100 Subject: [PATCH 16/18] [*] version bump. --- ayrton/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ayrton/__init__.py b/ayrton/__init__.py index e46593c..21b5c8f 100644 --- a/ayrton/__init__.py +++ b/ayrton/__init__.py @@ -47,7 +47,7 @@ from ayrton.parser.astcompiler.astbuilder import ast_from_node from ayrton.ast_pprinter import pprint -__version__= '0.7' +__version__= '0.7.1' class ExecParams: From 1c29c8b452a020c8ddca14a95054d9910e26da2b Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 10 Feb 2016 10:13:32 +0100 Subject: [PATCH 17/18] [+] The "From Russia with love" release. --- ChangeLog.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 59e7663..f6d323b 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,4 +1,12 @@ -ayrton (0.7) UNRELEASED; urgency=medium +ayrton (0.7.1) UNRELEASED; urgency=medium + + * Iterable parameters to executables are expanded in situ, so `foo(..., i, ...)` is expanded to `foo (..., i[0], i[1], ...` and `foo(..., k=i, ...)` is expanded to `foo (..., k=i[0], k=i[1], ...`. + * `-x|--trace` allows for minimal execution tracing. + * `-xx|--trace-with-linenos` allows for execution tracing that also prints the line number. + + -- Marcos Dione Wed, 03 Feb 2016 10:29:40 +0100 + +ayrton (0.7) unstable; urgency=medium * Send data to/from the remote via another `ssh` channel, which is more stable than using `stdin`. * Stabilized a lot all tests, specially those using a mocked stdout for getting test validation. @@ -11,7 +19,7 @@ ayrton (0.7) UNRELEASED; urgency=medium * `argv` is handled at the last time possible, allowing it being passed from test invoction. * `shift` complains on negative values. * Lazy `pprint()`, so debug statemens do not do useless work. - * `stdin/out/err` handling in `remote()` is done by a single thread. + * `stdin/out/err` handling in `remote()` is done by a single thread. * Modify a lot the local terminal when in `remote()` so, among other things, we have no local echo. * Properly pass the terminal type and size to the remote. These last three features allows programs like `vi` be run in the remote. * Paved the road to make `remote()`s more like `Command()`s. From a33a3aef9174194f69e58d4b55a90832932f3b3a Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 10 Feb 2016 10:14:41 +0100 Subject: [PATCH 18/18] [+] The "From Russia with love" release. --- ChangeLog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index f6d323b..b825d6f 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,10 +1,10 @@ -ayrton (0.7.1) UNRELEASED; urgency=medium +ayrton (0.7.1) unstable; urgency=medium * Iterable parameters to executables are expanded in situ, so `foo(..., i, ...)` is expanded to `foo (..., i[0], i[1], ...` and `foo(..., k=i, ...)` is expanded to `foo (..., k=i[0], k=i[1], ...`. * `-x|--trace` allows for minimal execution tracing. * `-xx|--trace-with-linenos` allows for execution tracing that also prints the line number. - -- Marcos Dione Wed, 03 Feb 2016 10:29:40 +0100 + -- Marcos Dione Wed, 10 Feb 2016 10:14:17 +0100 ayrton (0.7) unstable; urgency=medium