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