diff --git a/.gitignore b/.gitignore index fc8bb21b..8a28e883 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ /testall.log _build rebar.lock +.idea/ +.rebar3/ +.rebar/ +ebin/ +*.iml diff --git a/priv/python2/erlport/erlterms.py b/priv/python2/erlport/erlterms.py index 4246fac2..7e810c66 100644 --- a/priv/python2/erlport/erlterms.py +++ b/priv/python2/erlport/erlterms.py @@ -200,15 +200,23 @@ def decode_term(string, if not string: raise IncompleteData(string) tag = string[0] - if tag == "d": - # ATOM_EXT + if tag == "d" or tag == "w": + # ATOM_EXT, SMALL_ATOM_UTF8_EXT ln = len(string) - if ln < 3: - raise IncompleteData(string) - length = int2_unpack(string[1:3])[0] + 3 - if ln < length: - raise IncompleteData(string) - name = string[3:length] + if tag == "d": + if ln < 3: + raise IncompleteData(string) + length = int2_unpack(string[1:3])[0] + 3 + if ln < length: + raise IncompleteData(string) + name = string[3:length] + else: + if ln < 2: + raise IncompleteData(string) + length = ord(string[1]) + 2 + if ln < length: + raise IncompleteData(string) + name = string[2:length] if name == "true": return True, string[length:] elif name == "false": diff --git a/priv/python2/erlport/tests/erlterms_tests.py b/priv/python2/erlport/tests/erlterms_tests.py index 10d01063..7c4c0c9e 100644 --- a/priv/python2/erlport/tests/erlterms_tests.py +++ b/priv/python2/erlport/tests/erlterms_tests.py @@ -45,6 +45,8 @@ def test_atom(self): self.assertFalse(atom is Atom("test2")) self.assertTrue(atom is Atom("test")) self.assertEqual("X" * 255, Atom("X" * 255)) + self.assertEqual("\xc3\xa4", Atom("\xc3\xa4")) + self.assertEqual("\xe4", Atom("\xe4")) def test_invalid_atom(self): self.assertRaises(ValueError, Atom, "X" * 256) @@ -139,6 +141,7 @@ def test_decode(self): self.assertRaises(ValueError, decode, "\x83z") def test_decode_atom(self): + # ATOM_EXT: self.assertRaises(IncompleteData, decode, "\x83d") self.assertRaises(IncompleteData, decode, "\x83d\0") self.assertRaises(IncompleteData, decode, "\x83d\0\1") @@ -146,14 +149,31 @@ def test_decode_atom(self): self.assertEqual((Atom(""), "tail"), decode("\x83d\0\0tail")) self.assertEqual((Atom("test"), ""), decode("\x83d\0\4test")) self.assertEqual((Atom("test"), "tail"), decode("\x83d\0\4testtail")) + self.assertEqual((Atom("\xe4"), ""), decode("\x83d\0\1\xe4")) + # SMALL_ATOM_UTF8_EXT: + self.assertRaises(IncompleteData, decode, "\x83w") + self.assertRaises(IncompleteData, decode, "\x83w\1") + self.assertEqual((Atom(""), ""), decode("\x83w\0")) + self.assertEqual((Atom(""), "tail"), decode("\x83w\0tail")) + self.assertEqual((Atom("test"), ""), decode("\x83w\4test")) + self.assertEqual((Atom("test"), "tail"), decode("\x83w\4testtail")) + self.assertEqual((Atom("\xc3\xa4"), ""), decode("\x83w\2\xc3\xa4")) def test_decode_predefined_atoms(self): + # ATOM_EXT: self.assertEqual((True, ""), decode("\x83d\0\4true")) self.assertEqual((True, "tail"), decode("\x83d\0\4truetail")) self.assertEqual((False, ""), decode("\x83d\0\5false")) self.assertEqual((False, "tail"), decode("\x83d\0\5falsetail")) self.assertEqual((None, ""), decode("\x83d\0\11undefined")) self.assertEqual((None, "tail"), decode("\x83d\0\11undefinedtail")) + # SMALL_ATOM_UTF8_EXT: + self.assertEqual((True, ""), decode("\x83w\4true")) + self.assertEqual((True, "tail"), decode("\x83w\4truetail")) + self.assertEqual((False, ""), decode("\x83w\5false")) + self.assertEqual((False, "tail"), decode("\x83w\5falsetail")) + self.assertEqual((None, ""), decode("\x83w\11undefined")) + self.assertEqual((None, "tail"), decode("\x83w\11undefinedtail")) def test_decode_empty_list(self): self.assertEqual(([], ""), decode("\x83j")) diff --git a/priv/python3/.gitignore b/priv/python3/.gitignore index 9255de21..ebf0f2e4 100644 --- a/priv/python3/.gitignore +++ b/priv/python3/.gitignore @@ -1,3 +1 @@ .cover -erlport/__init__.py -erlport/cli.py diff --git a/priv/python3/erlport/__init__.py b/priv/python3/erlport/__init__.py new file mode 100644 index 00000000..d3b2367f --- /dev/null +++ b/priv/python3/erlport/__init__.py @@ -0,0 +1,33 @@ +# Copyright (c) 2009-2015, Dmitry Vasiliev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# 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. + +"""Erlang port protocol.""" + +__author__ = "Dmitry Vasiliev " +__version__ = "1.0" + +from erlport.erlterms import Atom, List, ImproperList diff --git a/priv/python3/erlport/cli.py b/priv/python3/erlport/cli.py new file mode 100644 index 00000000..49c3dc60 --- /dev/null +++ b/priv/python3/erlport/cli.py @@ -0,0 +1,77 @@ +# Copyright (c) 2009-2015, Dmitry Vasiliev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# 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. + +from optparse import OptionParser, OptionValueError + +from erlport import erlang +from erlport.erlproto import Port + + +def get_option_parser(): + def packet_option_handler(option, opt_str, value, parser): + if value not in (1, 2, 4): + raise OptionValueError("Valid values for --packet are 1, 2, or 4") + setattr(parser.values, option.dest, value) + + def compress_level(option, opt_str, value, parser): + if value < 0 or value > 9: + raise OptionValueError("Valid values for --compressed are 0..9") + setattr(parser.values, option.dest, value) + + def buffer_size(option, opt_str, value, parser): + if not value > 0: + raise OptionValueError("Buffer size value should be greater than 0") + setattr(parser.values, option.dest, value) + + parser = OptionParser(description="ErlPort - Erlang port protocol") + parser.add_option("--packet", action="callback", type="int", + help="Message length sent in N bytes. Valid values are 1, 2, or 4", + metavar="N", callback=packet_option_handler, default=4) + parser.add_option("--nouse_stdio", action="store_false", + dest="stdio", default=True, + help="Use file descriptors 3 and 4 for communication with Erlang") + parser.add_option("--use_stdio", action="store_true", dest="stdio", + default=True, + help="Use file descriptors 0 and 1 for communication with Erlang") + parser.add_option("--compressed", action="callback", type="int", default=0, + help="Compression level", metavar="LEVEL", callback=compress_level) + parser.add_option("--buffer_size", action="callback", type="int", + default=65536, help="Receive buffer size", metavar="SIZE", + callback=buffer_size) + return parser + + +def main(argv=None): + parser = get_option_parser() + options, args = parser.parse_args(argv) + port = Port(use_stdio=options.stdio, packet=options.packet, + compressed=options.compressed, buffer_size=options.buffer_size) + erlang.setup(port) + + +if __name__ == "__main__": + main() diff --git a/priv/python3/erlport/erlterms.py b/priv/python3/erlport/erlterms.py index 3f071348..7706a764 100644 --- a/priv/python3/erlport/erlterms.py +++ b/priv/python3/erlport/erlterms.py @@ -199,15 +199,23 @@ def decode_term(string, if not string: raise IncompleteData(string) tag = string[0] - if tag == 100: - # ATOM_EXT + if tag == 100 or tag == 119: + # ATOM_EXT, SMALL_ATOM_UTF8_EXT ln = len(string) - if ln < 3: - raise IncompleteData(string) - length = int2_unpack(string[1:3])[0] + 3 - if ln < length: - raise IncompleteData(string) - name = string[3:length] + if tag == 100: + if ln < 3: + raise IncompleteData(string) + length = int2_unpack(string[1:3])[0] + 3 + if ln < length: + raise IncompleteData(string) + name = string[3:length] + else: + if ln < 2: + raise IncompleteData(string) + length = string[1] + 2 + if ln < length: + raise IncompleteData(string) + name = string[2:length] if name == b"true": return True, string[length:] elif name == b"false": diff --git a/priv/python3/erlport/python.py b/priv/python3/erlport/python.py new file mode 100644 index 00000000..f3780aac --- /dev/null +++ b/priv/python3/erlport/python.py @@ -0,0 +1,58 @@ +# Copyright (c) 2009-2015, Dmitry Vasiliev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# 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. + +from erlport import Atom, erlang + + +# TODO: Base class should be extracted so we can use it for Ruby class too +class Python(object): + + def __init__(self, **kwargs): + options = self.parse_options(kwargs) + # TODO: Handle 'node' option with 'rpc' module + result = erlang.call(Atom("python"), Atom("start"), options) + if type(result) != tuple or len(result) != 2: + # TODO: Fix exception + raise Exception(result) + if result[0] is not Atom("ok"): + # TODO: Fix exception + raise Exception(result[1]) + self.pid = result[1] + + def parse_options(self, kwargs): + # TODO: Parse options + return [] + + def call(self, module, function, args): + # TODO: Check all arguments + # TODO: Reraise Python related exceptions + return erlang.call(Atom("python"), Atom("call"), + [self.pid, module, function, args]) + + def stop(self): + # TODO: Check result + erlang.call(Atom("python"), Atom("stop"), [self.pid]) diff --git a/priv/python3/erlport/tests/erlterms_tests.py b/priv/python3/erlport/tests/erlterms_tests.py index 86beedd6..7872963e 100644 --- a/priv/python3/erlport/tests/erlterms_tests.py +++ b/priv/python3/erlport/tests/erlterms_tests.py @@ -45,6 +45,8 @@ def test_atom(self): self.assertFalse(atom is Atom(b"test2")) self.assertTrue(atom is Atom(b"test")) self.assertEqual(b"X" * 255, Atom(b"X" * 255)) + self.assertEqual(b"\xc3\xa4", Atom(b"\xc3\xa4")) + self.assertEqual(b"\xe4", Atom(b"\xe4")) def test_invalid_atom(self): self.assertRaises(ValueError, Atom, b"X" * 256) @@ -139,6 +141,7 @@ def test_decode(self): self.assertRaises(ValueError, decode, b"\x83z") def test_decode_atom(self): + # ATOM_EXT: self.assertRaises(IncompleteData, decode, b"\x83d") self.assertRaises(IncompleteData, decode, b"\x83d\0") self.assertRaises(IncompleteData, decode, b"\x83d\0\1") @@ -146,14 +149,31 @@ def test_decode_atom(self): self.assertEqual((Atom(b""), b"tail"), decode(b"\x83d\0\0tail")) self.assertEqual((Atom(b"test"), b""), decode(b"\x83d\0\4test")) self.assertEqual((Atom(b"test"), b"tail"), decode(b"\x83d\0\4testtail")) + self.assertEqual((Atom(b"\xe4"), b""), decode(b"\x83d\0\1\xe4")) + # SMALL_ATOM_UTF8_EXT: + self.assertRaises(IncompleteData, decode, b"\x83w") + self.assertRaises(IncompleteData, decode, b"\x83w\1") + self.assertEqual((Atom(b""), b""), decode(b"\x83w\0")) + self.assertEqual((Atom(b""), b"tail"), decode(b"\x83w\0tail")) + self.assertEqual((Atom(b"test"), b""), decode(b"\x83w\4test")) + self.assertEqual((Atom(b"test"), b"tail"), decode(b"\x83w\4testtail")) + self.assertEqual((Atom(b"\xc3\xa4"), b""), decode(b"\x83w\2\xc3\xa4")) def test_decode_predefined_atoms(self): + # ATOM_EXT: self.assertEqual((True, b""), decode(b"\x83d\0\4true")) self.assertEqual((True, b"tail"), decode(b"\x83d\0\4truetail")) self.assertEqual((False, b""), decode(b"\x83d\0\5false")) self.assertEqual((False, b"tail"), decode(b"\x83d\0\5falsetail")) self.assertEqual((None, b""), decode(b"\x83d\0\11undefined")) self.assertEqual((None, b"tail"), decode(b"\x83d\0\11undefinedtail")) + # SMALL_ATOM_UTF8_EXT: + self.assertEqual((True, b""), decode(b"\x83w\4true")) + self.assertEqual((True, b"tail"), decode(b"\x83w\4truetail")) + self.assertEqual((False, b""), decode(b"\x83w\5false")) + self.assertEqual((False, b"tail"), decode(b"\x83w\5falsetail")) + self.assertEqual((None, b""), decode(b"\x83w\11undefined")) + self.assertEqual((None, b"tail"), decode(b"\x83w\11undefinedtail")) def test_decode_empty_list(self): self.assertEqual(([], b""), decode(b"\x83j")) diff --git a/ebin/erlport.app b/src/erlport.app.src similarity index 88% rename from ebin/erlport.app rename to src/erlport.app.src index 7e1ed1f9..ab0480c0 100644 --- a/ebin/erlport.app +++ b/src/erlport.app.src @@ -28,15 +28,10 @@ {application, erlport, [ {description, "Erlang port protocol"}, {vsn, "0.9.8"}, - {modules, [ - erlport, - erlport_options, - erlport_utils, - python, - python_options, - ruby, - ruby_options - ]}, + {modules, []}, {registered, []}, - {applications, [kernel, stdlib]} - ]}. + {applications, [kernel, stdlib]}, + {maintainers, ["Dmitry Vasiliev"]}, + {licenses, ["BSD-3-Clause"]}, + {links,[{"Github", "https://github.com/hdima/erlport"}]} +]}. diff --git a/src/python.hrl b/src/python.hrl index b31ca9e3..08b5d4e4 100644 --- a/src/python.hrl +++ b/src/python.hrl @@ -35,7 +35,7 @@ -record(python_options, { python = default :: string() | default, - cd :: Path :: string(), + cd :: Path :: string() | undefined, use_stdio = use_stdio :: use_stdio | nouse_stdio, compressed = 0 :: 0..9, packet = 4 :: 1 | 2 | 4, diff --git a/src/ruby.hrl b/src/ruby.hrl index ac6affd4..9eec6390 100644 --- a/src/ruby.hrl +++ b/src/ruby.hrl @@ -35,7 +35,7 @@ -record(ruby_options, { ruby = default :: string() | default, - cd :: Path :: string(), + cd :: Path :: string() | undefined, use_stdio = use_stdio :: use_stdio | nouse_stdio, compressed = 0 :: 0..9, packet = 4 :: 1 | 2 | 4,