Skip to content

Commit

Permalink
Adds -i/--import short import syntax.
Browse files Browse the repository at this point in the history
  • Loading branch information
unbrice committed Sep 29, 2015
1 parent c07db96 commit df65d18
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 15 deletions.
8 changes: 7 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Using ``{{`` ``}}`` instead of indentation, and ``;`` to separate statements:

.. code-block:: bash
py1 "import sys ; if True: {{ print(sys.version) }}"
py1 "a = 1+2; if a > 4: {{ print(a) }}"
The wrapper script defines a convenient set of 1&2-letters variables and functions.
Expand All @@ -57,6 +57,12 @@ For example, to count lines matching ``'$a*^'``:
py1 --begin "count=0" --each-line "if M('$a*^'): count += 1"
--end "P(count)"
Lastly the wrapper script provide a short notation to easily import modules.

.. code-block:: bash
py1 --import "math/*" "P(cos(pi))"
To learn more you can read the
`list of one letter functions and variables <http://py1.vleu.net/page/variables.html>`_
or just look at
Expand Down
3 changes: 2 additions & 1 deletion docs/contents.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ Structure of this website
intro
examples
variables
imports
internals

Indices and tables
==================

Expand Down
14 changes: 13 additions & 1 deletion docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
Examples
========

Unix timestamp
--------------

Formats a Unix timestamp (1443214640) as a human-readable string.

.. code:: bash
py1 -i 'time/ctime' 'P(ctime(1443214640))'
To do so we import ctime from the time module and print it.


Count blank lines
-----------------

Expand All @@ -23,7 +35,7 @@ Show the second and third fields of ``/etc/passwd``, a file whose fields are sep

.. code:: bash
cat /etc/passwd | py1 -b 'WS=":"' -l 'P(W[1:2])'
cat /etc/passwd | py1 -b 'WS=":"' -l 'P(W[1:2])'
Here we override WS to use the "``:``" separator of ``/etc/passwd``.

Expand Down
38 changes: 38 additions & 0 deletions docs/imports.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.. highlight:: bash


Imports
=======

``--i``/``--import`` is a shortcut to easily import external libraries.

Importing a module
------------------

The equivalent of ``import xyz`` is ``--import xyz``. It is equivalent to ``--begin import xyz``, just shorter.

.. code:: bash
py1 --begin 'import math' 'P(math.cos(math.pi))'
py1 --import 'math' 'P(math.cos(math.pi))'
py1 -i 'math' 'P(math.cos(math.pi))'
Importing specific symbols
--------------------------

The equivalent of ``from xyz import abc`` is ``--import xyz/abc``. You can import multiple functions with ``--import xyz/abc,def``

.. code:: bash
py1 --import 'math/cos,pi' 'P(cos(pi))'
py1 -i 'math/*' 'P(cos(pi))'
Importing with a specific name
------------------------------

The equivalent of ``import abc as ABC`` is ``--import abc:ABC``. You can rename specific symbols in the same way like ``--import xyz/abc:ABC``

.. code:: bash
py1 --import 'math:M' 'P(M.cos(M.pi))'
75 changes: 68 additions & 7 deletions docs/internals.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,73 @@
.. highlight:: bash


Internals
=========

This describes the implementation of py1, not its usage.
Unix timestamp
--------------

Formats a Unix timestamp (1443214640) as a human-readable string.

.. code:: bash
py1 -i 'time/ctime' 'P(ctime(1443214640))'
To do so we import ctime from the time module and print it.


Count blank lines
-----------------

Count the number of blank lines in a file.

.. code:: bash
py1 -b 'c=0' -l 'if not L: c += 1' -e 'P(c)'
Here we define an acumulator variable and increment it when the line satisfies a criteria.


Print 2nd and 3rd fields
------------------------

Show the second and third fields of ``/etc/passwd``, a file whose fields are separated by "``:``".

.. code:: bash
cat /etc/passwd | py1 -b 'WS=":"' -l 'P(W[1:2])'
Here we override WS to use the "``:``" separator of ``/etc/passwd``.


Show lines matching a regexp
----------------------------

Show lines matching the regexp '$a+^'.

.. code:: bash
py1 -l 'if M("$a+^", L): P(L)'
Here we use the M matching function to match the regexp.

Count blank lines again
-----------------------

Count the number of blank lines in a file.

.. code:: bash
py1 -e 'P(sum(1 if l else 0 for l in F))'
Here we do not set a per-line statement and instead have sum iterate over F.

Group by
--------

Given a file of '$name $value', with name being repeated, sum the values for each name.

.. autosummary::
:toctree: _autosummary
.. code:: bash
py1.curly
py1.runner
py1.template_reader
py1.tty
py1 -b 'd=defaultdict(int)' -l 'd[W[0]] += int(W[1])'
-e 'for n, v in d: P(n, v)'
4 changes: 3 additions & 1 deletion docs/man.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ For more examples, please see `http://py1.vleu.net/examples.html`. For the defin

Options
-------

-h, --help show an help message and exit

-i IMPORT, --import IMPORT imports modules described in abbreviated form

-b CODE, --begin CODE code run once first

-e CODE, --end CODE code run once at the end
Expand Down
68 changes: 68 additions & 0 deletions py1/imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright (c) 2013-2015, Brice Arnould <unbrice@vleu.net>
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following condition are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""Parses the import statements."""


class Error(Exception):

"""Base class for errors from this module."""


class BadShortSyntaxError(Error):

"""The user-provided code does not decode to valid code."""

def __init__(self, short_import, expanded_import):
msg ='Import statement %s means %s which is invalid.' % (
short_import, expanded_import)
super(BadShortSyntaxError, self).__init__(msg)
self.short_import = short_import
self.expanded_import = expanded_import


def expand_short(short_import):

# from_str: the (optional) part between "from" and "import"
if '/' in short_import:
from_str, imported_str = short_import.split('/', 1)
else:
from_str, imported_str = None, short_import

expanded_imported_str = imported_str.replace(':', ' as ')

if from_str:
res = 'from %s import %s' % (from_str, expanded_imported_str)
else:
res = 'import %s' % expanded_imported_str

try:
code = compile(res, '<py1:imports.expand_short>', 'exec')
except SyntaxError:
code = None

if not code:
raise BadShortSyntaxError(short_import, res)

return res
13 changes: 10 additions & 3 deletions py1/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from py1 import template_reader
from py1 import constants
from py1 import curly
from py1 import imports
from py1 import runner
from py1 import tty

Expand All @@ -51,6 +52,9 @@ def _get_option_parser():
metavar='PY', help='Code run each line.')
p.add_argument('-e', '--end', action='append', default=[],
metavar='PY', help='Code run once at the end.')
p.add_argument('-i', '--import', action='append', default=[],
dest='import_list', metavar='IMPORT',
help='Imports modules described in abbreviated form.')
p.add_argument('-c', '--dump-code', '--code',
default=False, # Value if not provided
const=_ARG_CONCISE, # Value if provided without argument
Expand Down Expand Up @@ -84,18 +88,21 @@ def _uncurl_list_or_die(escaped_list):
def main(args=None):
parser = _get_option_parser()
args = parser.parse_args(args)

begin = [imports.expand_short(i) for i in args.import_list]

if args.single_snippet:
# We expect no options if given a single snippet.
# We expect no additional code if given a single snippet.
for opt in ('begin', 'end', 'each_line'):
if getattr(args, opt):
snippet = _abreviate(args.single_snippet)
parser.error(
'--%s is specified yet there is a lonely code snippet, try'
' fixing quotes or adding --begin/-b before: "%s"' %
(opt, snippet))
begin = [args.single_snippet]
begin.append(args.single_snippet)
else:
begin = args.begin
begin += args.begin

code = template_reader.build_code(
begin=_uncurl_list_or_die(begin),
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class build(build.build):
py_modules=[
'py1.constants',
'py1.curly',
'py1.imports',
'py1.main',
'py1.runner',
],
Expand Down
67 changes: 67 additions & 0 deletions tests/test_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright (c) 2013-2015, Brice Arnould <unbrice@vleu.net>
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following condition are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""Tests for the imports module."""

import threading
import unittest

from py1 import imports


class TestExpand(unittest.TestCase):

def testGoodSamples(self):
samples = [
# The full syntax
('m1.m2.m3/i1:n1,i2:n2,i3', 'from m1.m2.m3 import i1 as n1,i2 as n2,i3'),
# No from
('i1:n1,i2:n2,i3', 'import i1 as n1,i2 as n2,i3'),
# Just one import
('i', 'import i'),
# Import *
('i/*', 'from i import *'),
]

for short_import, expanded_import in samples:
self.assertEqual(expanded_import, imports.expand_short(short_import))

def testInvalidSamples(self):
samples = [
# Extra "!"
'i!',
# Import star without scope
'*',
# Missing name
'a:',
# Missing module
':b',
]

for short_import in samples:
self.assertRaises(imports.BadShortSyntaxError, imports.expand_short, short_import)


if __name__ == '__main__':
unittest.main()
3 changes: 2 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ def testRaw(self):
def testAwkLike(self):
"""Test running valid code in awk-like mode."""
with self.ioPatcher():
main.main(['-b', 'import io; F=io.StringIO("85\\n12")',
main.main(['-i', 'io',
'-b', 'F=io.StringIO("85\\n12")',
'-b', 'count = 0',
'-l', 'count += int(L)',
'-e', 'P("count=", count)',
Expand Down

0 comments on commit df65d18

Please sign in to comment.