diff --git a/.gitignore b/.gitignore index f67702c..1da7871 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.py[co] +*~ .*.swp hello.txt tests/*/*.rb diff --git a/README.md b/README.md new file mode 100644 index 0000000..8390c0a --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +# py2rb.py + +A code translator using AST from Python to Ruby. This is basically a +NodeVisitor with Ruby output. See ast documentation +() for more information. + +## Installation + +Execute the following: +``` +pip install py2rb +``` +or +``` +git clone git://github.com/naitoh/py2rb.git +``` + +## Versions + +- Python 3.5 .. 3.8.7 +- Ruby 2.4 .. 3.0.0 + +## Dependencies + +### Python + + pip install six + pip install pyyaml + pip install numpy + +### Ruby + + gem install numo-narray + +## Methodology + +In addition to walking and writing the AST tree and writing a Ruby +syntax output, this tool either: +- Monkey-patches (or refines) some common Ruby Modules and Classes in order to emulate the Python equivalent. +- Calls equivalent Ruby methods to the python equivalent + +## Usage + +Sample 1: + + $ cat tests/basic/oo_inherit_simple.py + +```python +class bar(object): + + def __init__(self,name): + self.name = name + + def setname(self,name): + self.name = name + +class foo(bar): + + registered = [] + + def __init__(self,val,name): + self.fval = val + self.register(self) + self.name = name + + def inc(self): + self.fval += 1 + + def msg(self, a=None, b=None, c=None): + txt = '' + varargs = a, b, c + for arg in varargs: + if arg is None: + continue + txt += str(arg) + txt += "," + return txt + self.name + " says:"+str(self.fval) + + @staticmethod + def register(f): + foo.registered.append(f) + + @staticmethod + def printregistered(): + for r in foo.registered: + print(r.msg()) + +a = foo(10,'a') +a.setname('aaa') +b = foo(20,'b') +c = foo(30,'c') + +a.inc() +a.inc() +c.inc() + +print(a.msg()) +print(b.msg()) +print(c.msg(2,3,4)) + +print("---") + +foo.printregistered() +``` + +The above will result in : + + $ py2rb tests/basic/oo_inherit_simple.py + +```ruby +class Bar + def initialize(name) + @name = name + end + def setname(name) + @name = name + end +end +class Foo < Bar + def method_missing(method, *args) + self.class.__send__ method, *args + end + @@registered = [] + def initialize(val, name) + @fval = val + Foo.register(self) + @name = name + end + def inc() + @fval += 1 + end + def msg(a: nil, b: nil, c: nil) + txt = "" + varargs = [a, b, c] + for arg in varargs + if arg === nil + next + end + txt += arg.to_s + txt += "," + end + return ((txt + @name) + " says:") + @fval.to_s + end + def self.register(f) + @@registered.push(f) + end + def self.printregistered() + for r in @@registered + print(r.msg()) + end + end + def self.registered; @@registered; end + def self.registered=(val); @@registered=val; end + def registered; @registered = @@registered if @registered.nil?; @registered; end + def registered=(val); @registered=val; end +end +a = Foo.new(10, "a") +a.setname("aaa") +b = Foo.new(20, "b") +c = Foo.new(30, "c") +a.inc() +a.inc() +c.inc() +print(a.msg()) +print(b.msg()) +print(c.msg(a: 2, b: 3, c: 4)) +print("---") +Foo.printregistered() +``` + +Sample 2: + + $ cat tests/deep-learning-from-scratch/and_gate.py + +```python +# coding: utf-8 +import numpy as np + +def AND(x1, x2): + x = np.array([x1, x2]) + w = np.array([0.5, 0.5]) + b = -0.7 + tmp = np.sum(w*x) + b + if tmp <= 0: + return 0 + else: + return 1 + +if __name__ == '__main__': + for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]: + y = AND(xs[0], xs[1]) + print(str(xs) + " -> " + str(y)) +``` + +The above will result in : + + $ py2rb tests/deep-learning-from-scratch/and_gate.py +```ruby +require 'numo/narray' +def AND(x1, x2) + x = Numo::NArray.cast([x1, x2]) + w = Numo::NArray.cast([0.5, 0.5]) + b = -0.7 + tmp = ((w * x).sum()) + b + if tmp <= 0 + return 0 + else + return 1 + end +end + +if __FILE__ == $0 + for xs in [[0, 0], [1, 0], [0, 1], [1, 1]] + y = AND(xs[0], xs[1]) + print((xs.to_s + (" -> ")) + y.to_s) + end +end +``` +Sample 3 (Convert all local dependent module files of specified Python +file): +``` +$ git clone git://github.com/chainer/chainer.git +$ py2rb chainer/chainer/__init__.py -m -p chainer -r -w -f +Try : chainer/chainer/__init__.py -> chainer/chainer/__init__.rb : [OK] +Warning : yield is not supported : +Warning : yield is not supported : +Try : chainer/chainer/configuration.py -> chainer/chainer/configuration.rb : [Warning] +Try : chainer/chainer/cuda.py -> chainer/chainer/cuda.rb : [OK] + : + : +Try : chainer/chainer/utils/array.py -> chainer/chainer/utils/array.rb : [OK] +``` +## Tests +``` +$ ./run_tests.py +``` +Will run all tests, that are supposed to work. If any test fails, it\'s +a bug. +``` +$ ./run_tests.py -a +``` +Will run all tests including those that are known to fail (currently). +It should be understandable from the output. +``` +$ ./run_tests.py -x or $ ./run_tests.py --no-error +``` +Will run tests but ignore if an error is raised by the test. This is not +affecting the error generated by the test files in the tests directory. + +For additional information on flags, run: +``` +./run_tests.py -h +``` +## License + +MIT, see the LICENSE file for exact details. diff --git a/README.rst b/README.rst deleted file mode 100644 index 1417c4e..0000000 --- a/README.rst +++ /dev/null @@ -1,234 +0,0 @@ -py2rb.py -======== - -A code translator using AST from Python to Ruby. -This is basically a NodeVisitor with ruby output. -See ast documentation (https://docs.python.org/3/library/ast.html) for more information. - -Installation ------------- - -Execute the following:: - - pip install py2rb - - or - - git clone git://github.com/naitoh/py2rb.git - -Dependencies ------------- - -- Python 3.5, 3.6 -- Ruby 2.4 or later - - -Usage --------- - -Sample 1:: - - $ cat tests/basic/oo_inherit_simple.py - class bar(object): - - def __init__(self,name): - self.name = name - - def setname(self,name): - self.name = name - - class foo(bar): - - registered = [] - - def __init__(self,val,name): - self.fval = val - self.register(self) - self.name = name - - def inc(self): - self.fval += 1 - - def msg(self, a=None, b=None, c=None): - txt = '' - varargs = a, b, c - for arg in varargs: - if arg is None: - continue - txt += str(arg) - txt += "," - return txt + self.name + " says:"+str(self.fval) - - @staticmethod - def register(f): - foo.registered.append(f) - - @staticmethod - def printregistered(): - for r in foo.registered: - print(r.msg()) - - a = foo(10,'a') - a.setname('aaa') - b = foo(20,'b') - c = foo(30,'c') - - a.inc() - a.inc() - c.inc() - - print(a.msg()) - print(b.msg()) - print(c.msg(2,3,4)) - - print("---") - - foo.printregistered() - -The above will result in :: - - $ py2rb tests/basic/oo_inherit_simple.py - class Bar - def initialize(name) - @name = name - end - def setname(name) - @name = name - end - end - class Foo < Bar - def method_missing(method, *args) - self.class.__send__ method, *args - end - @@registered = [] - def initialize(val, name) - @fval = val - Foo.register(self) - @name = name - end - def inc() - @fval += 1 - end - def msg(a: nil, b: nil, c: nil) - txt = "" - varargs = [a, b, c] - for arg in varargs - if arg === nil - next - end - txt += arg.to_s - txt += "," - end - return ((txt + @name) + " says:") + @fval.to_s - end - def self.register(f) - @@registered.push(f) - end - def self.printregistered() - for r in @@registered - print(r.msg()) - end - end - def self.registered; @@registered; end - def self.registered=(val); @@registered=val; end - def registered; @registered = @@registered if @registered.nil?; @registered; end - def registered=(val); @registered=val; end - end - a = Foo.new(10, "a") - a.setname("aaa") - b = Foo.new(20, "b") - c = Foo.new(30, "c") - a.inc() - a.inc() - c.inc() - print(a.msg()) - print(b.msg()) - print(c.msg(a: 2, b: 3, c: 4)) - print("---") - Foo.printregistered() - -Sample 2:: - - $ cat tests/deep-learning-from-scratch/and_gate.py - # coding: utf-8 - import numpy as np - - def AND(x1, x2): - x = np.array([x1, x2]) - w = np.array([0.5, 0.5]) - b = -0.7 - tmp = np.sum(w*x) + b - if tmp <= 0: - return 0 - else: - return 1 - - if __name__ == '__main__': - for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]: - y = AND(xs[0], xs[1]) - print(str(xs) + " -> " + str(y)) - -The above will result in :: - - $ py2rb tests/deep-learning-from-scratch/and_gate.py - require 'numo/narray' - def AND(x1, x2) - x = Numo::NArray.cast([x1, x2]) - w = Numo::NArray.cast([0.5, 0.5]) - b = -0.7 - tmp = ((w * x).sum()) + b - if tmp <= 0 - return 0 - else - return 1 - end - end - - if __FILE__ == $0 - for xs in [[0, 0], [1, 0], [0, 1], [1, 1]] - y = AND(xs[0], xs[1]) - print((xs.to_s + (" -> ")) + y.to_s) - end - end - -Sample 3 (Convert all local dependent module files of specified Python file):: - - $ git clone git://github.com/chainer/chainer.git - $ py2rb chainer/chainer/__init__.py -m -p chainer -r -w -f - Try : chainer/chainer/__init__.py -> chainer/chainer/__init__.rb : [OK] - Warning : yield is not supported : - Warning : yield is not supported : - Try : chainer/chainer/configuration.py -> chainer/chainer/configuration.rb : [Warning] - Try : chainer/chainer/cuda.py -> chainer/chainer/cuda.rb : [OK] - : - : - Try : chainer/chainer/utils/array.py -> chainer/chainer/utils/array.rb : [OK] - -Tests ------ - -$ ./run_tests.py - -Will run all tests, that are supposed to work. If any test fails, it's a bug. - -$ ./run_tests.py -a - -Will run all tests including those that are known to fail (currently). It -should be understandable from the output. - -$ ./run_tests.py -x -or -$ ./run_tests.py --no-error - -Will run tests but ignore if an error is raised by the test. This is not -affecting the error generated by the test files in the tests directory. - -For more flags then described here - -./run_tests.py -h - - -License -------- - -MIT, see the LICENSE file for exact details. diff --git a/py2rb/__init__.py b/py2rb/__init__.py index f315dbc..02ba317 100644 --- a/py2rb/__init__.py +++ b/py2rb/__init__.py @@ -26,7 +26,7 @@ class RB(object): yaml_files = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'modules/*.yaml') for filename in glob.glob(yaml_files): with open(filename, 'r') as f: - module_map.update(yaml.load(f)) + module_map.update(yaml.safe_load(f)) using_map = { 'EnumerableEx' : False, @@ -362,7 +362,7 @@ def visit(self, node, scope=None): else: if self._mode == 1: self.set_result(1) - sys.stderr.write("Warning : syntax not supported (%s)\n" % node) + sys.stderr.write("Warning : syntax not supported (%s line:%d col:%d\n" % (node, node.lineno, node.col_offset)) return '' if hasattr(visitor, 'statement'): @@ -1580,6 +1580,29 @@ def visit_DictComp(self, node): (i, t, self.visit(node.generators[0].ifs[0]), t, \ self.visit(node.key), self.visit(node.value)) + def visit_SetComp(self, node): + """ + SetComp(expr elt, comprehension* generators) + """ + i = self.visit(node.generators[0].iter) # ast.Tuple, ast.List, ast.* + if isinstance(node.generators[0].target, ast.Name): + t = self.visit(node.generators[0].target) + else: + # ast.Tuple + self._tuple_type = '' + t = self.visit(node.generators[0].target) + self._tuple_type = '[]' + if len(node.generators[0].ifs) == 0: + """ [x**2 for x in {1,2}] + [1, 2].map{|x| x**2}.to_set """ + return "%s.map{|%s| %s}.to_set" % (i, t, self.visit(node.elt)) + else: + """ [x**2 for x in {1,2} if x > 1] + {1, 2}.select {|x| x > 1 }.map{|x| x**2}.to_set """ + return "%s.select{|%s| %s}.map{|%s| %s}.to_set" % \ + (i, t, self.visit(node.generators[0].ifs[0]), t, \ + self.visit(node.elt)) + def visit_Lambda(self, node): """ Lambda(arguments args, expr body) @@ -1728,6 +1751,19 @@ def visit_Ellipsis(self, node): """ return 'false' + + # Python 3.8+ uses ast.Constant instead of ast.NamedConstant + def visit_Constant(self, node): + value = node.value + if value is True or value is False or value is None: + return self.name_constant_map[value] + elif isinstance(value, str): + return self.visit_Str(node) + elif node.value == Ellipsis: + return self.visit_Ellipsis(node) + else: + return repr(node.s) + def visit_Str(self, node): """ Str(string s) @@ -1735,12 +1771,36 @@ def visit_Str(self, node): # Uses the Python builtin repr() of a string and the strip string type from it. txt = re.sub(r'"', '\\"', repr(node.s)[1:-1]) txt = re.sub(r'\\n', '\n', txt) + txt = re.sub(r'#([$@{])', r'\#\1', txt) if self._is_string_symbol: txt = ':' + txt else: txt = '"' + txt + '"' return txt - + + def visit_JoinedStr(self, node): + txt = "" + for val in node.values: + if self.name(val) == "FormattedValue": + txt = txt + "#{" + self.visit(val) + "}" + else: + txt = txt + self.visit(val)[1:-1] + return '"' + txt + '"' + + def visit_FormattedValue(self, node): + conversion = node.conversion + if conversion == 97: + # ascii + raise NotImplementedError("Cannot handle {!a} f-string conversions yet") + elif conversion == 114: + # repr + return self.visit(node.value) + ".inspect" + elif conversion == 115: + # string + return self.visit(node.value) + ".to_s" + else: + return self.visit(node.value) + def key_list_check(self, key_list, rb_args): j = 0 star = 0 @@ -1915,10 +1975,12 @@ def get_methods_map(self, method_map, rb_args=False, ins=False): print("get_methods_map key : %s not match method_map['val'][key] %s" % (key, method_map['val'][key])) if len(args_hash) == 0: self.set_result(2) - raise RubyError("methods_map defalut argument Error : not found args") + raise RubyError("methods_map default argument Error : not found args") if 'main_data_key' in method_map: data_key = method_map['main_data_key'] + if not data_key in args_hash: + raise Exception("Error: Missing key '%s' from args_hash" % data_key) main_data = args_hash[data_key] if 'main_func' in method_map.keys(): @@ -2153,6 +2215,8 @@ def visit_Call(self, node): rb_args_s = '' elif len(rb_args) == 1: rb_args_s = rb_args[0] + elif hasattr(rb_args[0], "decode"): + rb_args_s = b", ".join(rb_args) else: rb_args_s = ", ".join(rb_args) @@ -2572,6 +2636,10 @@ def visit_List(self, node): return "[%s]" % (", ".join(els)) #return ", ".join(els) + def visit_Set(self, node): + els = [self.visit(e) for e in node.elts] + return "Set.new([%s])" % (", ".join(els)) + def visit_ExtSlice(self, node): """ ExtSlice(slice* dims) @@ -2656,7 +2724,7 @@ def visit_Yield(self, node): if self._mode == 1: self.set_result(1) sys.stderr.write("Warning : yield is not supported : %s\n" % self.visit(node.value)) - return "yield %s" % (self.visit(node.value)) + return "yield(%s)" % (self.visit(node.value)) else: if self._mode == 1: self.set_result(1) diff --git a/py2rb/builtins/module.rb b/py2rb/builtins/module.rb index 2965bd0..33e7673 100644 --- a/py2rb/builtins/module.rb +++ b/py2rb/builtins/module.rb @@ -1,4 +1,45 @@ # frozen_string_literal: true + +class Method + # Monkeypath Method#to_s to provide a consistent output for multiple + # versions of ruby. This is dirty, but easiest way to proceed without + # ripping up the test suite to provide several different expected + # outputs for all the changes from 2.4 .. 3.0 + # This will probably come back to bite me.... + def to_s + loc = self.source_location + return "#<#{self.class}: #{self.owner}\##{self.original_name}>" + end +end + +# For some reason monkeypatching Set after the Enumerable refinement +# doesnt work for ruby 2.7.0 .. 2.7.2, so moving it up here. +# this seems like a ruby bug, and apparently is fixed in 3.0.0 +# + + +# +# Foo.call() or Foo.() is nothing => Foo.new() call. +# +class Class + def method_missing(method, *args) + if method == :call + self.new(*args) + else + super + end + end +end + +require 'set' +class Set + def remove(x) + self.delete(x) + return + end +end + + module PythonZipEx refine Object do # python : zip(l1, l2, [l3, ..]) @@ -244,23 +285,3 @@ def self.walk(dir) end end -# -# Foo.call() or Foo.() is nothing => Foo.new() call. -# -class Class - def method_missing(method, *args) - if method == :call - self.new(*args) - else - super - end - end -end - -require 'set' -class Set - def remove(x) - self.delete(x) - return - end -end diff --git a/py2rb/modules/unittest.yaml b/py2rb/modules/unittest.yaml index 85f9d52..28a86d9 100644 --- a/py2rb/modules/unittest.yaml +++ b/py2rb/modules/unittest.yaml @@ -7,7 +7,7 @@ # mod_name: Ruby module namespace : %(mod)s # e.g. mod_name: Numo # from numpy import array => require 'numo/narray'; include Numo # mod: mod_name + '::' # mod_class_name: -# Pyth n class name: Ruby class name # For class inheritance +# Python class name: Ruby class name # For class inheritance # e.g. TestCase: 'Test::Unit::TestCase' # # methods_map: @@ -87,6 +87,19 @@ unittest: first: True second: True msg: True + TestCase.assertNotIn: + # (first, second, msg=None) + main_func: 'assert_not_include' + key: + - [first, second, msg] + key_order: + # self.assertIn('foo', ['foo', 'bar'], 'message test') + # assert_include(['foo', 'bar'],'foo','message test') + - [second, first, msg] + val: + first: True + second: True + msg: True TestCase.assertEqual: # (first, second, msg=None) main_func: 'assert_equal' @@ -98,6 +111,17 @@ unittest: first: True second: True msg: True + TestCase.assertNotEqual: + # (first, second, msg=None) + main_func: 'assert_not_equal' + key: + - [first, second, msg] + key_order: + - [second, first, msg] + val: + first: True + second: True + msg: True TestCase.assertAlmostEqual: # (first, second, places=7, msg=None, delta=None) main_func: 'assert_in_delta' @@ -117,6 +141,25 @@ unittest: places: '1e-1*1e-%(places)s' msg: True delta: True + TestCase.assertNotAlmostEqual: + # (first, second, places=7, msg=None, delta=None) + main_func: 'assert_not_in_delta' + key: + - [first, second, places, msg, delta] + key_order: + # - places case + # self.assertAlmostEqual(1.0, 1.00000001, 7, '''comment test''') + # assert_in_delta(1.00000001, 1.0, 1e-1*1e-7, "comment test") + # - delta case + # self.assertAlmostEqual(1.0, 1.00000001, msg='''comment test''', delta=1e-8) + # assert_in_delta(1.00000001, 1.0, 1e-08, "comment test") + - [second, first, places, delta, msg] + val: + first: True + second: True + places: '1e-1*1e-%(places)s' + msg: True + delta: True TestCase.assertIsInstance: # (obj, cls, msg=None) key: @@ -136,6 +179,25 @@ unittest: # assertIsInstance(obj, cls, msg) # assert_true obj.is_a?(cls), msg - ' (%(obj)s).is_a?(%(cls)s), %(msg)s' + TestCase.assertIsNotInstance: + # (obj, cls, msg=None) + key: + - [obj, cls] + - [obj, cls, msg] + val: + obj: True # Use argument data + cls: True # Use argument data + msg: True # Use argument data + main_func: 'assert_false' + rtn: # rtn[0]-rtn[3] + - '' + - '' + # assertIsInstance(obj, cls) + # assert_true obj.is_a?(cls) + - ' (%(obj)s).is_a?(%(cls)s)' + # assertIsInstance(obj, cls, msg) + # assert_true obj.is_a?(cls), msg + - ' (%(obj)s).is_a?(%(cls)s), %(msg)s' TestCase.assertIs: # (first, second, msg=None) main_func: 'assert_equal' @@ -147,6 +209,29 @@ unittest: first: True second: True msg: True + TestCase.assertIsNot: + # (first, second, msg=None) + main_func: 'assert_not_equal' + key: + - [first, second, msg] + key_order: + - [second, first, msg] + val: + first: True + second: True + msg: True + + TestCase.assertIsNone: + # (first, msg=None) + main_func: 'assert_nil' + key: + - [first, msg] + key_order: + - [first, msg] + val: + first: True + msg: True + TestCase.assertTrue: # (expr, msg=None) main_func: 'assert' @@ -155,6 +240,14 @@ unittest: val: expr: True msg: True + TestCase.assertFalse: + # (expr, msg=None) + main_func: 'assert_false' + key: + - [expr, msg] + val: + expr: True + msg: True TestCase.assertRaises: # (exception, callable, *args, **kwds) main_func: 'assert_raise' @@ -177,6 +270,110 @@ unittest: - "(%(exception)s){%(callable)s()}" - "(%(exception)s){%(callable)s(%(*args)s)}" - "(%(exception)s){%(callable)s(%(*args)s, %(**kwds)s)}" + TestCase.assertGreater: + # (first, second, msg=None) + key: + - [first, second] + - [first, second, msg] + val: + first: True # Use argument data + second: True # Use argument data + msg: True # Use argument data + main_func: 'assert_compare' + rtn: # rtn[0]-rtn[3] + - '' + - '' + # assertGreater(first, second) + # assert_compare(expected, '>', actual) + - "((%(first)s), '>', (%(second)s))" + # assertGreater(first, second, msg) + # assert_compare(expected, '>', actual) + - "((%(first)s), '>', (%(second)s), %(msg)s)" + TestCase.assertGreaterEqual: + # (first, second, msg=None) + key: + - [first, second] + - [first, second, msg] + val: + first: True # Use argument data + second: True # Use argument data + msg: True # Use argument data + main_func: 'assert_compare' + rtn: # rtn[0]-rtn[3] + - '' + - '' + # assertGreaterEqual(first, second) + # assert_compare(expected, '>=', actual) + - "((%(first)s), '>=', (%(second)s))" + # assertGreaterEqual(first, second, msg) + # assert_compare(expected, '>=', actual) + - "((%(first)s), '>=', (%(second)s), %(msg)s)" + TestCase.assertLess: + # (first, second, msg=None) + key: + - [first, second] + - [first, second, msg] + val: + first: True # Use argument data + second: True # Use argument data + msg: True # Use argument data + main_func: 'assert_compare' + rtn: # rtn[0]-rtn[3] + - '' + - '' + # assertLess(first, second) + # assert_compare(expected, '<', actual) + - "((%(first)s), '<', (%(second)s))" + # assertLess(first, second, msg) + # assert_compare(expected, '<', actual) + - "((%(first)s), '<', (%(second)s), %(msg)s)" + TestCase.assertLessEqual: + # (first, second, msg=None) + key: + - [first, second] + - [first, second, msg] + val: + first: True # Use argument data + second: True # Use argument data + msg: True # Use argument data + main_func: 'assert_compare' + rtn: # rtn[0]-rtn[3] + - '' + - '' + # assertLessEqual(first, second) + # assert_compare(expected, '<=', actual) + - "((%(first)s), '<=', (%(second)s))" + # assertLessEqual(first, second, msg) + # assert_compare(expected, '<=', actual) + - "((%(first)s), '<=', (%(second)s), %(msg)s)" ignore: main: '' +# Asserts from pydoc that are supported here: +# +# [x] assertEqual(a, b) ==> a == b +# [x] assertNotEqual(a, b) ==> a != b +# [x] assertTrue(x) ==> bool(x) is True +# [x] assertFalse(x) ==> bool(x) is False +# [x] assertIs(a, b) ==> a is b +# [x] assertIsNot(a, b) ==> a is not b +# assertIsNone(x) ==> x is None +# assertIsNotNone(x) ==> x is not None +# [x] assertIn(a, b) ==> a in b +# [x] assertNotIn(a, b) ==> a not in b +# [x] assertIsInstance(a, b) ==> isinstance(a, b) +# [x] assertNotIsInstance(a, b) ==> not isinstance(a, b) +# [x] assertRaises(exc, fun, *args, **kwds) ==> fun(*args, **kwds) raises exc +# assertRaisesRegex(exc, r, fun, *args, **kwds) ==> fun(*args, **kwds) raises exc and the message matches regex r +# assertWarns(warn, fun, *args, **kwds) ==> fun(*args, **kwds) raises warn +# assertWarnsRegex(warn, r, fun, *args, **kwds) ==> fun(*args, **kwds) raises warn and the message matches regex r +# assertLogs(logger, level) ==> The with block logs on logger with minimum level +# [x] assertAlmostEqual(a, b) ==> round(a-b, 7) == 0 +# [x] assertNotAlmostEqual(a, b) ==> round(a-b, 7) != 0 +# [x] assertGreater(a, b) ==> a > b +# [x] assertGreaterEqual(a, b) ==> a >= b +# [x] assertLess(a, b) ==> a < b +# [x] assertLessEqual(a, b) ==> a <= b +# assertRegex(s, r) ==> r.search(s) +# assertNotRegex(s, r) ==> not r.search(s) +# assertCountEqual(a, b) ==> a and b have the same elements in the same number, regardless of their order. diff --git a/run_tests.py b/run_tests.py index a36c637..f7b805d 100755 --- a/run_tests.py +++ b/run_tests.py @@ -5,6 +5,18 @@ import testtools.util import testtools.tests +try: + import yaml +except: + raise "Cannot find pyyaml, install and re-try" + +try: + import numpy +except: + raise "Cannot find numpy, install and re-try" + + + def main(): option_parser = optparse.OptionParser( usage="%prog [options] [filenames]", diff --git a/setup.py b/setup.py index 9048715..b74b00e 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ here = path.abspath(path.dirname(__file__)) -with open(path.join(here, 'README.rst'), encoding='utf-8') as f: +with open(path.join(here, 'README.md'), encoding='utf-8') as f: long_description = f.read() setup( @@ -31,6 +31,8 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', ], keywords='python ruby', packages=['py2rb'], diff --git a/tests/strings/fstrings.py b/tests/strings/fstrings.py new file mode 100644 index 0000000..fcfb0a6 --- /dev/null +++ b/tests/strings/fstrings.py @@ -0,0 +1,26 @@ +a = "dog\ndog\tdog" +b = 'cat' +c = '🐱' + +print(f"001 {a}{b}{c}") +print(f'002 {a}{b}{c}') +print(f"""003 {a}{b}{c}""") + +print(f"004 repr {a!r} {b!r} {c!r}") +print(f"005 string {a!s} {b!s} {c!s}") +#broken print(f"006 ascii {a!a} {b!a} {c!a}") + +c = 1.234E-6 +d = -177 + +print(f"float: {c} int: {d}") + +e = (1, 3, 5) +f = ["b", 'c', 'd', 7] + +print(f'tuple: {e} list: {f}') + +print(f'tuple: {e!s} list: {f!s}') +print(f'tuple: {e!r} list: {f!r}') +#broken print(f'tuple: {e!a} list: {f!a}') + diff --git a/tests/strings/fstrings.rb.expected_out b/tests/strings/fstrings.rb.expected_out new file mode 100644 index 0000000..c7049c8 --- /dev/null +++ b/tests/strings/fstrings.rb.expected_out @@ -0,0 +1,13 @@ +001 dog +dog dogcat🐱 +002 dog +dog dogcat🐱 +003 dog +dog dogcat🐱 +004 repr "dog\ndog\tdog" "cat" "🐱" +005 string dog +dog dog cat 🐱 +float: 1.234e-06 int: -177 +tuple: [1, 3, 5] list: ["b", "c", "d", 7] +tuple: [1, 3, 5] list: ["b", "c", "d", 7] +tuple: [1, 3, 5] list: ["b", "c", "d", 7] diff --git a/tests/unittest/assertAlmostEqual.py b/tests/unittest/assertAlmostEqual.py index b78da46..f778198 100644 --- a/tests/unittest/assertAlmostEqual.py +++ b/tests/unittest/assertAlmostEqual.py @@ -2,10 +2,15 @@ class TestRunnable(unittest.TestCase): - def test_runnable(self): + def test_almeq(self): #(first, second, places=7, msg=None, delta=None) self.assertAlmostEqual(1.0, 1.00000001, 7) self.assertAlmostEqual(1.0, 1.00000001, 7, '''comment test''') self.assertAlmostEqual(1.0, 1.00000001, msg='''comment test''', delta=1e-8) + def test_notalmeq(self): + #(first, second, places=7, msg=None, delta=None) + self.assertNotAlmostEqual(1.5, 1.00000001, 7) + self.assertNotAlmostEqual(1.5, 1.00000001, 7, '''comment test''') + self.assertNotAlmostEqual(1.5, 1.00000001, msg='''comment test''', delta=1e-8) unittest.main() diff --git a/tests/unittest/assertAlmostEqual.rb.expected_in_out b/tests/unittest/assertAlmostEqual.rb.expected_in_out index 8bc8a02..aa89cc5 100644 --- a/tests/unittest/assertAlmostEqual.rb.expected_in_out +++ b/tests/unittest/assertAlmostEqual.rb.expected_in_out @@ -1 +1 @@ -1 tests, 3 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications +2 tests, 6 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications diff --git a/tests/unittest/assertCompare.py b/tests/unittest/assertCompare.py new file mode 100644 index 0000000..fdd83ee --- /dev/null +++ b/tests/unittest/assertCompare.py @@ -0,0 +1,20 @@ +import unittest + +class TestRunnable(unittest.TestCase): + + def test_gt(self): + self.assertGreater(10, 1, '''comment test''') + self.assertGreater(10, 1) + def test_lt(self): + self.assertLess(1, 10, '''comment test''') + self.assertLess(1, 10) + def test_gte(self): + self.assertGreaterEqual(11, 10, '''comment test''') + self.assertGreaterEqual(11, 10) + self.assertGreaterEqual(10, 10) + def test_lte(self): + self.assertLessEqual(1, 10, '''comment test''') + self.assertLessEqual(1, 10) + self.assertLessEqual(1, 1) + +unittest.main() diff --git a/tests/unittest/assertCompare.rb.expected_in_out b/tests/unittest/assertCompare.rb.expected_in_out new file mode 100644 index 0000000..a4d0c44 --- /dev/null +++ b/tests/unittest/assertCompare.rb.expected_in_out @@ -0,0 +1 @@ +4 tests, 10 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications diff --git a/tests/unittest/assertEqual.py b/tests/unittest/assertEqual.py index aedc22b..b6c5ae9 100644 --- a/tests/unittest/assertEqual.py +++ b/tests/unittest/assertEqual.py @@ -2,7 +2,11 @@ class TestRunnable(unittest.TestCase): - def test_runnable(self): + def test_eq(self): self.assertEqual('test', 'test', '''comment test''') + self.assertEqual(1, 1) + def test_neq(self): + self.assertNotEqual(7, 8, '''comment test''') + self.assertNotEqual(1, 2) unittest.main() diff --git a/tests/unittest/assertEqual.rb.expected_in_out b/tests/unittest/assertEqual.rb.expected_in_out index e527062..6b7710b 100644 --- a/tests/unittest/assertEqual.rb.expected_in_out +++ b/tests/unittest/assertEqual.rb.expected_in_out @@ -1 +1 @@ -1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications +2 tests, 4 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications diff --git a/tests/unittest/assertTrue.rb.expected_in_out b/tests/unittest/assertFalse.rb.expected_in_out similarity index 100% rename from tests/unittest/assertTrue.rb.expected_in_out rename to tests/unittest/assertFalse.rb.expected_in_out diff --git a/tests/unittest/assertIn.py b/tests/unittest/assertIn.py index 9ad3c5f..9f0c68e 100644 --- a/tests/unittest/assertIn.py +++ b/tests/unittest/assertIn.py @@ -4,5 +4,10 @@ class Test_Unit(unittest.TestCase): def test_hoge(self): self.assertIn('foo', ['foo', 'bar'], 'message test') + self.assertIn('foo', ['foo', 'bar']) + + def test_notin(self): + self.assertNotIn('baz', ['foo', 'bar'], 'message test') + self.assertNotIn('baz', ['foo', 'bar']) unittest.main() diff --git a/tests/unittest/assertIn.rb.expected_in_out b/tests/unittest/assertIn.rb.expected_in_out index e527062..6b7710b 100644 --- a/tests/unittest/assertIn.rb.expected_in_out +++ b/tests/unittest/assertIn.rb.expected_in_out @@ -1 +1 @@ -1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications +2 tests, 4 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications diff --git a/tests/unittest/assertNotEqual.rb.expected_in_out b/tests/unittest/assertNotEqual.rb.expected_in_out new file mode 100644 index 0000000..e527062 --- /dev/null +++ b/tests/unittest/assertNotEqual.rb.expected_in_out @@ -0,0 +1 @@ +1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications diff --git a/tests/unittest/assertTrue.py b/tests/unittest/assertTrue.py deleted file mode 100644 index 606f4bc..0000000 --- a/tests/unittest/assertTrue.py +++ /dev/null @@ -1,8 +0,0 @@ -import unittest - -class TestRunnable(unittest.TestCase): - - def test_runnable(self): - self.assertTrue(True, '''comment test''') - -unittest.main() diff --git a/tests/unittest/assertTrueFalseNone.py b/tests/unittest/assertTrueFalseNone.py new file mode 100644 index 0000000..39fa911 --- /dev/null +++ b/tests/unittest/assertTrueFalseNone.py @@ -0,0 +1,17 @@ +import unittest + +class TestRunnable(unittest.TestCase): + + def test_true(self): + self.assertTrue(True, '''comment test''') + self.assertTrue(True) + + def test_false(self): + self.assertFalse(False, '''comment test''') + self.assertFalse(False) + + def test_isnone(self): + self.assertIsNone(None, '''comment test''') + self.assertIsNone(None) + +unittest.main() diff --git a/tests/unittest/assertTrueFalseNone.rb.expected_in_out b/tests/unittest/assertTrueFalseNone.rb.expected_in_out new file mode 100644 index 0000000..b17c216 --- /dev/null +++ b/tests/unittest/assertTrueFalseNone.rb.expected_in_out @@ -0,0 +1 @@ +3 tests, 6 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications