diff --git a/Makefile b/Makefile
index ced5ffa..1679101 100644
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,11 @@ docs:
install: tests
python3 setup.py install --prefix=$(INSTALL_DIR)
+unsafe-install:
+ echo "unsafe install, are you sure?"
+ read foo
+ python3 setup.py install --prefix=$(INSTALL_DIR)
+
upload: tests upload-docs
python3 setup.py sdist upload
diff --git a/README.md b/README.md
index 2e01e88..029d0a4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-`ayrton` - a shell-like scripting language based on Python3.
+`ayrton` - a shell-like scripting language strongly based on Python3.
-`ayrton` is an extension of the Python language that tries to make it look more
+`ayrton` is an modification of the Python language that tries to make it look more
like a shell programming language. It takes ideas already present in `sh`, adds
a few functions for better emulating envvars, and provides a mechanism for (semi)
transparent remote execution via `ssh`.
@@ -54,7 +54,7 @@ So, in short:
# First steps: execution, output
-To mimic the second example in the introduction,
+To do the same as the second example in the introduction,
with `sh` you could `from sh import echo` and it will create a callable that will
transparently run `/bin/echo` for you; `ayrton` takes a step further and creates
the callable on the fly, so you don't have to pre-declare it. Another difference
@@ -73,18 +73,16 @@ Just guess were the output went :) ... (ok, ok, it went to `/dev/null`).
# Composing
-Just like `sh`, you can nest callables, but you must explicitly tell it that you
-want to capture the output so the nesting callable gets its input:
+Just like `sh`, you can nest callables:
- root= grep (cat ('/etc/passwd', _out=Capture), 'root', _out=Capture)
+ root= grep (cat ('/etc/passwd'), 'root', _out=Capture)
-This seems more cumbersome than `sh`, but if you think that in any shell language
-you do something similar (either using `$()`, `|` or even redirection), it's not
-a high price to pay.
+In the special case where a command is the first argument for another, its output
+will be captured and piped to the stdin of the outer command.
Another improvement over `sh` is that you can use commands as conditions:
- if grep (cat ('/etc/passwd', _out=Capture), 'mdione', _out=None):
+ if grep (cat ('/etc/passwd'), 'mdione', _out=None):
print ('user «mdione» is present on your system; that's a security vulnerability right there!')
As a consequence, you can also use `and`, `or` and `not`.
@@ -97,9 +95,7 @@ we had to implement it:
if cat ('/etc/passwd') | grep ('mdione', _out=None):
print ('I'm here, baby!')
-Notice that this time you don't have to be explicit about the `cat`'s output;
-we know it's going to a pipe, so we automatically `Capture` it. Of course, we
-also have redirection:
+And of course, we also have redirection:
grep ('mdione') < '/etc/passwd' > '/tmp/foo'
grep ('root') < '/etc/passwd' >> '/tmp/foo'
@@ -119,7 +115,7 @@ of:
/home/mdione/src/projects/ayrton/bin
/home/mdione/src/projects/ayrton
-`bash()` applies brace, tilde and glob (pathname) expansions:
+The `bash()` function applies brace, tilde and glob (pathname) expansions:
>>> from ayrton.expansion import bash
>>> import os
@@ -184,14 +180,17 @@ If the latter fails the construct fails and your script will finish. We're
checking its limitations to see where we can draw the line of what will be
possible or not.
+The development of this construct is not complete, so expect some changes in its
+API.
+
Here you'll find [the docs](http://www.grulic.org.ar/~mdione/projects/ayrton/).
# FAQ
Q: Why bother? Isn't `bash` great?
-A: Yes and no. `bash` is very powerful, both from the CLI and as a language. But
-it's clumsy, mainly due to two reasons: parsing lines into commands and their
+A: Yes and no. `bash` is very powerful, both from the CLI point of view and as a language.
+But it's clumsy, mainly due to two reasons: parsing lines into commands and their
arguments, and the methods for preventing overzealous word splitting, which leads
to several pitfalls, some of them listed [here](http://mywiki.wooledge.org/BashPitfalls));
and poor data manipulation syntax. It also lacks of good remote
diff --git a/TODO.rst b/TODO.rst
index 8ba467d..484ed88 100644
--- a/TODO.rst
+++ b/TODO.rst
@@ -10,6 +10,8 @@ Really do:
* from foo import bar is gonna break
+* imported ayrton scripts should be parsed with the ayrton parser.
+
* becareful with if cat () | grep (); error codes must be carried too
* process substitution
@@ -21,7 +23,7 @@ Really do:
* see pdb's source
* becareful with buitins, might eclipse valid usages: bash() (exp) blocks /bin/bash
-
+ * rename bash() to expand()
* add option _exec=True for actually executing the binary.
* check ``bash``'s manpage and see what's missing.
@@ -31,9 +33,17 @@ Really do:
* a setting for making references to unkown envvars as in bash.
* trap?
+* executable path caching à la bash.
-If we {have time,are bored}:
-----------------------------
+Think deeply about:
+-------------------
* what to do about relative/absolute command paths?
-* executable path caching à la bash.
+* git (commit) vs git.commit() vs git ('commit')
+* function names are expressions too:
+ * / as unary op? => /path/to/excecutable and relative/path
+ * foo_bar vs foo__bar vs foo-bar
+ * -f vs (-)f vs _f
+* commands in keywords should also be _out=Capture
+* which is the sanest default, bash (..., single=True) or otherwise
+* foo(-l, --long-option)?
diff --git a/ayrton/ast_pprinter.py b/ayrton/ast_pprinter.py
index 3a447ee..be20f0d 100644
--- a/ayrton/ast_pprinter.py
+++ b/ayrton/ast_pprinter.py
@@ -603,6 +603,9 @@ def pprint_inner (node, level=0):
yield ' as '
for i in pprint_inner (node.optional_vars): yield i
+ elif t==str:
+ yield node
+
else:
yield '\n'
yield '# unknown construction\n'
diff --git a/ayrton/castt.py b/ayrton/castt.py
index ea1b207..4c74729 100644
--- a/ayrton/castt.py
+++ b/ayrton/castt.py
@@ -50,6 +50,9 @@ def is_executable (node):
type (node.func.func)==Name and
node.func.func.id=='Command')
+def is_option (arg):
+ return type (arg)==Call and type (arg.func)==Name and arg.func.id=='o'
+
def has_keyword (node, keyword):
return any ([kw.arg==keyword for kw in node.keywords])
@@ -465,6 +468,13 @@ def visit_Call (self, node):
node.args.pop (0)
update_keyword (node, keyword (arg='_in', value=first_arg))
+ for arg in node.args:
+ if is_option (arg):
+ # ast_pprinter takes care of expressions
+ kw= arg.keywords[0]
+ logger.debug ("->>>kw: %s", ast.dump (kw))
+ kw.arg= pprint (kw.arg)
+
ast.copy_location (new_node, node)
node.func= new_node
ast.fix_missing_locations (node)
@@ -482,19 +492,30 @@ def visit_Call (self, node):
first_kw= False
for index, arg in enumerate (node.args):
# NOTE: maybe o() can be left in its own namespace so it doesn't pollute
- if type (arg)==Call and type (arg.func)==Name and arg.func.id=='o':
- kw_name= arg.keywords[0].arg
+ if is_option (arg):
+ kw_expr= arg.keywords[0].arg
+ if not isinstance (kw_expr, ast.Name) and not isinstance (kw_expr, str):
+ raise SyntaxError (self.file_name, node.lineno, node.column,
+ "keyword can't be an expression")
+
+ if isinstance (kw_expr, ast.Name):
+ kw_name= kw_expr.id
+ else:
+ kw_name= kw_expr # str
+
if kw_name in used_keywords:
- raise SyntaxError(self.file_name, node.lineno, node.column,
- "keyword argument repeated")
+ raise SyntaxError (self.file_name, node.lineno, node.column,
+ "keyword argument repeated")
- node.keywords.append (arg.keywords[0])
+ # convert the expr into a str
+ new_kw= keyword (kw_name, arg.keywords[0].value)
+ node.keywords.append (new_kw)
used_keywords.add (kw_name)
first_kw= True
else:
if first_kw:
- raise SyntaxError(self.file_name, node.lineno, node.column,
- "non-keyword arg after keyword arg")
+ raise SyntaxError (self.file_name, node.lineno, node.column,
+ "non-keyword arg after keyword arg")
new_args.append (arg)
diff --git a/ayrton/execute.py b/ayrton/execute.py
index 05d75de..96e66f0 100644
--- a/ayrton/execute.py
+++ b/ayrton/execute.py
@@ -288,16 +288,15 @@ def prepare_args (self, cmd, args, kwargs):
def prepare_arg (self, seq, name, value):
if value!=False:
- if len (name)==1:
- arg="-%s" % name
- else:
- # TODO: longopt_prefix
- # and/or simply subclass find(Command)
- arg="--%s" % name
- seq.append (arg)
+ 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))
+ else:
+ # TODO: --no-option?
+ pass
def parent (self):
if self.stdin_pipe is not None:
@@ -355,6 +354,7 @@ def wait (self):
self.capture_file= open (r)
if self._exit_code==127:
+ # NOTE: when running bash, it returns 127 when it can't find the script to run
raise CommandNotFound (self.path)
if (ayrton.runner.options.get ('errexit', False) and
diff --git a/ayrton/expansion.py b/ayrton/expansion.py
index 8a428d9..64076d4 100644
--- a/ayrton/expansion.py
+++ b/ayrton/expansion.py
@@ -41,9 +41,6 @@ def glob_expand (s):
# accumulate them
ans+= a
- if len(ans)==1:
- ans= ans[0]
-
return ans
class Group (object):
@@ -202,9 +199,6 @@ def brace_expand (s):
else:
ans.append (te.text)
- if len(ans)==1:
- ans= ans[0]
-
return ans
def backslash_descape (s):
@@ -225,5 +219,8 @@ def tilde_expand (s):
return ans
-def bash (s):
- return backslash_descape (glob_expand (tilde_expand (brace_expand (s))))
+def bash (s, single=False):
+ data= backslash_descape (glob_expand (tilde_expand (brace_expand (s))))
+ if single and len(data)==1:
+ data= data[0]
+ return data
diff --git a/ayrton/parser/astcompiler/astbuilder.py b/ayrton/parser/astcompiler/astbuilder.py
index b24d8d3..6feb2ec 100644
--- a/ayrton/parser/astcompiler/astbuilder.py
+++ b/ayrton/parser/astcompiler/astbuilder.py
@@ -1187,9 +1187,6 @@ def handle_call(self, args_node, callable_expr):
if argument.type == syms.argument:
if len(argument.children) == 1:
expr_node = argument.children[0]
- # if keywords:
- # self.error("non-keyword arg after keyword arg",
- # expr_node)
if variable_arg:
self.error("only named arguments may follow "
"*expression", expr_node)
@@ -1202,21 +1199,22 @@ def handle_call(self, args_node, callable_expr):
if isinstance(keyword_expr, ast.Lambda):
self.error("lambda cannot contain assignment",
keyword_node)
- elif not isinstance(keyword_expr, ast.Name):
- self.error("keyword can't be an expression",
- keyword_node)
- keyword = keyword_expr.id
- self.check_forbidden_name(keyword, keyword_node)
+ keyword = keyword_expr
+ if isinstance (keyword, ast.Name):
+ self.check_forbidden_name(keyword.id, keyword_node)
keyword_value = self.handle_expr(argument.children[2])
- if keyword in Command.supported_options:
- keywords.append(ast.keyword(keyword, keyword_value))
+ if isinstance (keyword, ast.Name) and keyword.id in Command.supported_options:
+ keywords.append(ast.keyword(keyword.id, keyword_value))
else:
kw = ast.keyword(keyword, keyword_value)
kw.lineno = keyword_node.lineno
+ kw.col_offset = keyword_node.column
name = ast.Name ('o', ast.Load())
name.lineno = keyword_node.lineno
+ name.column = keyword_node.column
arg = ast.Call(name, [], [ kw ], None, None)
arg.lineno = keyword_node.lineno
+ arg.column = keyword.column
args.append(arg)
elif argument.type == tokens.STAR:
variable_arg = self.handle_expr(args_node.children[i + 1])
diff --git a/ayrton/tests/test_ayrton.py b/ayrton/tests/test_ayrton.py
index 7c55df5..308954c 100644
--- a/ayrton/tests/test_ayrton.py
+++ b/ayrton/tests/test_ayrton.py
@@ -35,10 +35,16 @@
class Bash(unittest.TestCase):
def test_simple_string (self):
- self.assertEqual (bash ('s'), 's')
+ self.assertEqual (bash ('s'), [ 's' ])
+
+ def test_simple_string_single (self):
+ self.assertEqual (bash ('s', single=True), 's')
def test_glob1 (self):
- self.assertEqual (bash ('*.py'), 'setup.py')
+ self.assertEqual (bash ('*.py'), [ 'setup.py' ])
+
+ def test_glob1_single (self):
+ self.assertEqual (bash ('*.py', single=True), 'setup.py')
def test_glob2 (self):
self.assertEqual (sorted (bash ([ '*.py', '*.txt' ])), [ 'LICENSE.txt', 'setup.py', ])
@@ -56,10 +62,16 @@ def test_simple2_brace (self):
self.assertEqual (bash ('a{b,ce}d'), [ 'abd', 'aced' ])
def test_simple3_brace (self):
- self.assertEqual (bash ('{a}'), '{a}')
+ self.assertEqual (bash ('{a}'), [ '{a}' ])
+
+ def test_simple3_brace_single (self):
+ self.assertEqual (bash ('{a}', single=True), '{a}')
def test_simple4_brace (self):
- self.assertEqual (bash ('a}'), 'a}')
+ self.assertEqual (bash ('a}'), [ 'a}' ])
+
+ def test_simple4_brace_single (self):
+ self.assertEqual (bash ('a}', single=True), 'a}')
def test_simple5_brace (self):
self.assertEqual (bash ('a{bfgh,{ci,djkl}e'), [ 'a{bfgh,cie', 'a{bfgh,djkle' ])
@@ -78,14 +90,20 @@ def test_nested2_brace (self):
self.assertEqual (bash ('{c{a,b}d,e{f,g}h}'), [ 'cad', 'cbd', 'efh', 'egh' ])
def test_escaped_brace (self):
- self.assertEqual (bash ('\{a,b}'), '{a,b}')
+ self.assertEqual (bash ('\{a,b}'), [ '{a,b}' ])
+
+ def test_escaped_brace_single (self):
+ self.assertEqual (bash ('\{a,b}', single=True), '{a,b}')
def test_real_example1 (self):
# tiles/{legend*,Elevation.dgml,preview.png,Makefile}
pass
def test_tilde (self):
- self.assertEqual (bash ('~'), os.environ['HOME'])
+ self.assertEqual (bash ('~'), [ os.environ['HOME'] ])
+
+ def test_tilde_single (self):
+ self.assertEqual (bash ('~', single=True), os.environ['HOME'])
def setUpMockStdout (self):
# due to the interaction between file descriptors,
@@ -203,7 +221,7 @@ def testPipe (self):
self.assertEqual (self.r.read (), b'setup.py\n')
def testLongPipe (self):
- ayrton.main ('ls () | grep ("setup") | wc (l=True)')
+ ayrton.main ('ls () | grep ("setup") | wc (-l=True)')
# close stdout as per the description of setUpMockStdout()
os.close (1)
self.assertEqual (self.r.read (), b'1\n')
diff --git a/ayrton/tests/test_castt.py b/ayrton/tests/test_castt.py
index 40deb8c..d3e85b1 100644
--- a/ayrton/tests/test_castt.py
+++ b/ayrton/tests/test_castt.py
@@ -107,20 +107,42 @@ def testDoubleKeywordCommand (self):
# keywords=[], starargs=None, kwargs=None)
# both arguments have the same name!
self.assertEqual (node.args[0].keywords[0].arg,
- node.args[1].keywords[0].arg, ast.dump (node))
+ node.args[1].keywords[0].arg,
+ "\n%s\n%s\n" % (ast.dump (node.args[0]), ast.dump (node.args[1])))
def testDoubleKeywordFunction (self):
c= castt.CrazyASTTransformer ({ 'o': o, 'dict': dict})
- t= ayrton.parse ("""dict (p= True, p=False)""")
+ t= ayrton.parse ("""dict (p=True, p=False)""")
self.assertRaises (SyntaxError, c.visit_Call, t.body[0].value)
def testKeywordAfterPosFunction (self):
c= castt.CrazyASTTransformer ({ 'o': o, 'dict': dict})
- t= ayrton.parse ("""dict (p= True, False)""")
+ t= ayrton.parse ("""dict (p=True, False)""")
self.assertRaises (SyntaxError, c.visit_Call, t.body[0].value)
+ def testMinusMinusFunction (self):
+ c= castt.CrazyASTTransformer ({ 'o': o, 'dict': dict})
+ t= ayrton.parse ("""dict (--p=True)""")
+
+ self.assertRaises (SyntaxError, c.visit_Call, t.body[0].value)
+
+ def testMinusMinusCommand (self):
+ c= castt.CrazyASTTransformer ({ 'o': o})
+ t= ayrton.parse ("""foo (--p=True)""")
+
+ node= c.visit_Call (t.body[0].value)
+
+ self.assertEqual (node.args[0].keywords[0].arg, '--p')
+
+ def testLongOptionCommand (self):
+ c= castt.CrazyASTTransformer ({ 'o': o})
+ t= ayrton.parse ("""foo (--long-option=True)""")
+
+ node= c.visit_Call (t.body[0].value)
+
+ self.assertEqual (node.args[0].keywords[0].arg, '--long-option')
class TestHelperFunctions (unittest.TestCase):
@@ -137,7 +159,6 @@ def testDottedName (self):
self.assertEqual (combined, 'test.py')
def testDottedDottedName (self):
- # NOTE: yes, indentation sucks here
single, combined= castt.func_name2dotted_exec (parse_expression ('test.me.py'))
self.assertEqual (single, 'test')
diff --git a/ayrton/tests/test_execute.py b/ayrton/tests/test_execute.py
index 9857d5b..4ff61c9 100644
--- a/ayrton/tests/test_execute.py
+++ b/ayrton/tests/test_execute.py
@@ -30,7 +30,6 @@
ls= Command ('ls')
ssh= Command ('ssh')
mcedit= Command ('mcedit')
-bash= Command ('bash')
true= Command ('true')
false= Command ('false')
grep= Command ('grep')
@@ -110,28 +109,15 @@ def testOutNone (self):
self.assertEqual (self.mock_stdout.read (), '')
self.mock_stdout.close ()
- def testKwargsAsUnorderedOptions (self):
- echo (l=True, more=42, kwargs_as_unordered_options='yes!')
+ def testOrderedOptions (self):
+ ayrton.main ("""echo (-l=True, --more=42, --ordered_options='yes!')""")
tearDownMockStdOut (self)
-
- output= self.mock_stdout.read ()
- # we can't know for sure the order of the options in the final command line
- # '-l --more 42 --kwargs_as_unordered_options yes!\n'
- self.assertTrue ('-l' in output)
- self.assertTrue ('--more 42' in output)
- self.assertTrue ('--kwargs_as_unordered_options yes!' in output)
- self.assertTrue (output[-1]=='\n')
- self.mock_stdout.close ()
-
- def testOOrdersOptions (self):
- echo (o(l=True), o(more=42), o(o_orders_options='yes!'))
- tearDownMockStdOut (self)
- self.assertEqual (self.mock_stdout.read (), '-l --more 42 --o_orders_options yes!\n')
+ self.assertEqual (self.mock_stdout.read (), '-l --more 42 --ordered_options yes!\n')
self.mock_stdout.close ()
def testEnvironment (self):
# NOTE: we convert envvars to str when we export() them
- bash (c='echo environments works: $FOO', _env=dict (FOO='yes'))
+ ayrton.main ("""sh (-c='echo environments works: $FOO', _env=dict (FOO='yes'))""")
tearDownMockStdOut (self)
self.assertEqual (self.mock_stdout.read (), 'environments works: yes\n')
self.mock_stdout.close ()
diff --git a/contrib/kate/ayrton.xml b/contrib/kate/ayrton.xml
index a896119..a71ae7d 100644
--- a/contrib/kate/ayrton.xml
+++ b/contrib/kate/ayrton.xml
@@ -168,6 +168,15 @@
- stderr
- Capture
- path
+ - _in
+ - _out
+ - _err
+ - _end
+ - _comp
+ - _encoding
+ - _env
+ - _bg
+ - _fails
- __new__
diff --git a/doc/idea.txt b/doc/idea.txt
index d547741..6c79a65 100644
--- a/doc/idea.txt
+++ b/doc/idea.txt
@@ -9,9 +9,9 @@
16:17 < StucKman> debería pegarle otra mirada
16:18 < StucKman> porque ls () | wc ('-l') no me suena muy feo
16:18 < StucKman> si se pueden hacer cosas así, hay que pensárselo
-16:19 < StucKman> sobre todo si derepente a un comando en particular lo podés luego implementar como una función que en vez del output en texto te devuelve una estructura mas piola de
+* 16:19 < StucKman> sobre todo si derepente a un comando en particular lo podés luego implementar como una función que en vez del output en texto te devuelve una estructura mas piola de
usar
-16:19 < StucKman> hmmm
+* 16:19 < StucKman> hmmm
16:23 < rbistolfi> complicado de generalizar eso
16:24 < StucKman> rbistolfi: uhm?
16:26 < rbistolfi> digo, para un comando en particular ya se puede hacer no?
@@ -24,23 +24,23 @@
16:28 < facundobatista> StucKman, qué parte?
16:28 < StucKman> bah, puede ser
-16:29 < StucKman> pero me interesa el hecho de que derepente a wc() la puedas implementar como una función que devuelve una terna en vez de un string con tres enteros
-16:30 < StucKman> es decir, (1, 2, 3) en vez de "1 2 3"
+* 16:29 < StucKman> pero me interesa el hecho de que derepente a wc() la puedas implementar como una función que devuelve una terna en vez de un string con tres enteros
+* 16:30 < StucKman> es decir, (1, 2, 3) en vez de "1 2 3"
16:30 < StucKman> con sh() se podría...
16:30 < StucKman> hmmm
16:31 < facundobatista> StucKman, el tema ahí es lo que dice rbistolfi, un perno para generalizar
-16:31 < facundobatista> necesitás código para wc, otro para ls, otro para grep, otro para find, otro para...
+* 16:31 < facundobatista> necesitás código para wc, otro para ls, otro para grep, otro para find, otro para...
16:31 < rbistolfi> claro, si es para wc nomas, lo haces con popen
-16:31 < StucKman> el único tema es que derepente todo lo que sea ejecución de comandos pasa a ser un ciudadano de segunda
-16:32 < StucKman> facundobatista: of course you do :)
-16:32 < StucKman> pero la mayoría no necesitan
-16:32 < StucKman> grep no necesita, ls capaz sea overkill, etc
-16:32 < StucKman> find tampoco
-16:32 < StucKman> pero wc es un caso simple, ifconfig ya me parece mas interesante
-16:33 < StucKman> y no, no son generalizables, cada uno es su propio quilombo
-16:34 < StucKman> por ejemplo, ls a secas, mejor andá por el lado de os.readdir()
-16:34 < StucKman> ls -l, ok, pero es readdir+stat
-16:34 < StucKman> y así
+* 16:31 < StucKman> el único tema es que derepente todo lo que sea ejecución de comandos pasa a ser un ciudadano de segunda
+* 16:32 < StucKman> facundobatista: of course you do :)
+* 16:32 < StucKman> pero la mayoría no necesitan
+* 16:32 < StucKman> grep no necesita, ls capaz sea overkill, etc
+* 16:32 < StucKman> find tampoco
+* 16:32 < StucKman> pero wc es un caso simple, ifconfig ya me parece mas interesante
+* 16:33 < StucKman> y no, no son generalizables, cada uno es su propio quilombo
+* 16:34 < StucKman> por ejemplo, ls a secas, mejor andá por el lado de os.readdir()
+* 16:34 < StucKman> ls -l, ok, pero es readdir+stat
+* 16:34 < StucKman> y así
16:34 < Zzzoom> StucKman: por gente como vos existe emacs
16:34 < facundobatista> es que si vas a hacer un código para find, que acepte los parámetros
16:34 < StucKman> Zzzoom: hehehehe
@@ -515,4 +515,4 @@
('', 1, '', None)]
* shutil
-
+
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 6376859..20a4093 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -70,10 +70,13 @@ Commands do not raise Exceptions
When you run a command with ``sh`` and it either exited with a return code
different from 0 or it was stopped/finished by a signal, it would raise an
exception. You then can recover the exit code and the output from the
- exception. In ``ayrton`` not only exceptions are not raised, you can ask the
+ exception. In ``ayrton`` not only exceptions are not raised by defaut, you can ask the
exit code in the ``code`` attribute and the output in the ``stdout`` and
``stderr`` attributes. You can also use commands as booleans in conditions
- to ``if``, ``while``, etc: ``if ls(): then echo ("yes!")``.
+ to ``if``, ``while``, etc: ``if ls(): then echo ("yes!")``. Exceptions are raised when
+ the command is not found; or when the `errexit` :py:func:`option` is set, a
+ command returns an exit code not 0, and ``_fails`` was not specified. See also
+ :py:func:`foo`.
Commands with dots are supported
You can call `foo.py()`, no extra work needed.
@@ -98,4 +101,6 @@ enough that the benefits will overweight this.
an empty list, or a list with two or more elements.
* If you name a variable with the same name as an executable, you can't execute it until
you're out of that scope. This is exactly the same thing that happens when you
- eclipse a Python variable from an outer scope.
+ eclipse a Python variable from an outer scope, and similar to when you define a function
+ in `bash` with the same names as the executable (but that you can go around by giving
+ the full path if you know it, which you can't do in `ayrton`).
diff --git a/doc/source/reference.rst b/doc/source/reference.rst
index ca27087..8058e58 100644
--- a/doc/source/reference.rst
+++ b/doc/source/reference.rst
@@ -10,7 +10,7 @@ from shell like languages.
.. py:data:: argv
This variable holds a list of the arguments passed to the script, with the
- script's path in the first position.
+ script's path in the first position. In Python-speak, this is ``sys.argv``.
.. py:data:: path
@@ -42,7 +42,7 @@ Functions
that order). *list_or_str* can be a string or a list of strings. The return
value can be an empty list, a single string, or a list of two or more strings.
-.. py:function:: cd (path)
+.. py:function:: cd (path) | chdir (path)
Changes the current working directory for the script process. If used as a
context manager, it restores the original path when the context finishes,
@@ -57,7 +57,7 @@ Functions
.. py:function:: o (name=value)
Creates a positional option that will be expanded as an option/value when
- running a command. See :py:func:`foo`.
+ running a command. See :py:func:`foo`. This is mostly used internally.
.. py:function:: option (opt, value=True)
@@ -117,13 +117,10 @@ Functions
Executes the binary *foo*, searching the binary using :py:data:`path`.
Arguments in *\*args* are used as positional arguments for the command. This
- returns a :py:class:`Command`.
- If one is an :py:func:`o(k=v)`, it's replaced by two positional arguments,
- `-k` (or `--k` if `k` is longer than one character) and a second one `v`.
- The rest of the *\*\*kwargs* are added in the same way as :py:func:`o`,
- but in an arbitrary order, except for the following items, which are not
- passed but drive how the command is executed and where does input come from
- and output goes to:
+ 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')`
.. py:attribute:: _in
diff --git a/release.ay b/release.ay
index 21da7d2..f62533d 100755
--- a/release.ay
+++ b/release.ay
@@ -6,18 +6,17 @@ option ('-e')
if make ('tests'):
# this command might fail if it wants to
- uncommited= git ('status', '--short') | grep ('^ M', _out=Capture, _fails=True)
+ uncommited= git ('status', --short=True) | grep ('^ M', _out=Capture, _fails=True)
if uncommited:
print ("The following files are not commited, I can't release like this")
print (str (uncommited))
exit (1)
- dch (newversion=ayrton.__version__, changelog='ChangeLog.rst')
+ dch (--newversion=ayrton.__version__, --changelog='ChangeLog.rst')
# docs
make ('docs')
- # rsync --archive --verbose --compress --rsh ssh doc/build/html/ www.grulic.org.ar:www/projects/ayrton/
- rsync ('--archive', '--verbose', '--compress', '--rsh', 'ssh',
+ rsync (--archive=True, --verbose=True, --compress=True, --rsh='ssh',
'doc/build/html/', 'www.grulic.org.ar:www/projects/ayrton/')
# release