2021-03-19 09:14:59 +01:00

1329 lines
36 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017-2021 Sébastien Helleu <flashcode@flashtux.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
"""
Unparse AST tree to generate scripts in all supported languages
(Python, Perl, Ruby, ...).
"""
# pylint: disable=too-many-lines,unnecessary-pass,useless-object-inheritance
from __future__ import print_function
import argparse
import ast
import inspect
import os
import select
try:
from StringIO import StringIO # python 2
except ImportError:
from io import StringIO # python 3
import sys
sys.dont_write_bytecode = True
class UnparsePython(object):
"""
Unparse AST to generate Python script code.
This class is inspired from unparse.py in cpython:
https://github.com/python/cpython/blob/master/Tools/parser/unparse.py
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def __init__(self, output=sys.stdout):
self.output = output
self.indent_string = ' ' * 4
self._indent_level = 0
self._prefix = [] # not used in Python, only in other languages
self.binop = {
'Add': '+',
'Sub': '-',
'Mult': '*',
'MatMult': '@',
'Div': '/',
'Mod': '%',
'LShift': '<<',
'RShift': '>>',
'BitOr': '|',
'BitXor': '^',
'BitAnd': '&',
'FloorDiv': '//',
'Pow': '**',
}
self.unaryop = {
'Invert': '~',
'Not': 'not',
'UAdd': '+',
'USub': '-',
}
self.cmpop = {
'Eq': '==',
'NotEq': '!=',
'Lt': '<',
'LtE': '<=',
'Gt': '>',
'GtE': '>=',
'Is': 'is',
'IsNot': 'is not',
'In': 'in',
'NotIn': 'not in',
}
def fill(self, string=''):
"""Add a new line and an indented string."""
self.add('\n%s%s' % (self.indent_string * self._indent_level, string))
def indent(self):
"""Indent code."""
self._indent_level += 1
def unindent(self):
"""Unindent code."""
self._indent_level -= 1
def prefix(self, prefix):
"""Add or remove a prefix from list."""
if prefix:
self._prefix.append(prefix)
else:
self._prefix.pop()
def add(self, *args):
"""Add string/node(s) to the output file."""
for arg in args:
if callable(arg):
arg()
elif isinstance(arg, tuple):
arg[0](*arg[1:])
elif isinstance(arg, list):
for item in arg:
self.add(item)
elif isinstance(arg, ast.AST):
method = getattr(
self, '_ast_%s' % arg.__class__.__name__.lower(),
None)
if method is None:
raise NotImplementedError(arg)
method(arg)
elif isinstance(arg, str):
self.output.write(arg)
@staticmethod
def make_list(values, sep=', '):
"""Add multiple values using a custom method and separator."""
result = []
for value in values:
if result:
result.append(sep)
result.append(value)
return result
def is_bool(self, node): # pylint: disable=no-self-use
"""Check if the node is a boolean."""
return isinstance(node, ast.Name) and node.id in ('False', 'True')
def is_number(self, node): # pylint: disable=no-self-use
"""Check if the node is a number."""
# in python 2, number -1 is Num(n=-1)
# in Python 3, number -1 is UnaryOp(op=USub(), operand=Num(n=1))
return (isinstance(node, ast.Num) or
(isinstance(node, ast.UnaryOp) and
isinstance(node.op, (ast.UAdd, ast.USub))))
def _ast_alias(self, node):
"""Add an AST alias in output."""
# ignore alias
pass
def _ast_arg(self, node):
"""Add an AST arg in output."""
self.add('%s%s' % (self._prefix[-1] if self._prefix else '',
node.arg))
def _ast_assign(self, node):
"""Add an AST Assign in output."""
self.add(
self.fill,
[[target, ' = '] for target in node.targets],
node.value,
)
def _ast_attribute(self, node):
"""Add an AST Attribute in output."""
self.add(node.value, '.', node.attr)
def _ast_binop(self, node):
"""Add an AST BinOp in output."""
self.add(
node.left,
' %s ' % self.binop[node.op.__class__.__name__],
node.right,
)
def _ast_call(self, node):
"""Add an AST Call in output."""
self.add(
node.func,
'(',
self.make_list(node.args),
')',
)
def _ast_compare(self, node):
"""Add an AST Compare in output."""
self.add(node.left)
for operator, comparator in zip(node.ops, node.comparators):
self.add(
' %s ' % self.cmpop[operator.__class__.__name__],
comparator,
)
def _ast_constant(self, node):
"""Add an AST Constant in output."""
self.add(repr(node.s))
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'{',
self.make_list([[key, ': ', value]
for key, value in zip(node.keys, node.values)]),
'}',
)
def _ast_expr(self, node):
"""Add an AST Expr in output."""
if not isinstance(node.value, ast.Str): # ignore docstrings
self.add(
self.fill,
node.value,
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
self.fill if self._indent_level == 0 else None,
'def %s(' % node.name,
self.make_list(node.args.args),
'):',
self.indent,
node.body,
self.unindent,
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'if ',
node.test,
':',
self.indent,
node.body,
self.unindent,
)
if node.orelse:
self.add(
self.fill,
'else:',
self.indent,
node.orelse,
self.unindent,
)
def _ast_import(self, node):
"""Add an AST Import in output."""
# ignore import
pass
def _ast_module(self, node):
"""Add an AST Module in output."""
self.add(node.body)
def _ast_name(self, node):
"""Add an AST Name in output."""
self.add('%s%s' % (self._prefix[-1] if self._prefix else '',
node.id))
def _ast_num(self, node):
"""Add an AST Num in output."""
# note: deprecated since Python 3.8, replaced by ast.Constant
self.add(repr(node.n))
def _ast_pass(self, node): # pylint: disable=unused-argument
"""Add an AST Pass in output."""
self.fill('pass')
def _ast_return(self, node):
"""Add an AST Return in output."""
self.fill('return')
if node.value:
self.add(' ', node.value)
def _ast_str(self, node):
"""Add an AST Str in output."""
# note: deprecated since Python 3.8, replaced by ast.Constant
self.add(repr(node.s))
def _ast_tuple(self, node):
"""Add an AST Tuple in output."""
self.add(
'(',
self.make_list(node.elts),
',' if len(node.elts) == 1 else None,
')',
)
def _ast_unaryop(self, node):
"""Add an AST UnaryOp in output."""
self.add(
'(',
self.unaryop[node.op.__class__.__name__],
' ',
node.operand,
')',
)
class UnparsePerl(UnparsePython):
"""
Unparse AST to generate Perl script code.
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def _ast_assign(self, node):
"""Add an AST Assign in output."""
self.add(
self.fill,
(self.prefix, '%' if isinstance(node.value, ast.Dict) else '$'),
[[target, ' = '] for target in node.targets],
(self.prefix, None),
node.value,
';',
)
def _ast_attribute(self, node):
"""Add an AST Attribute in output."""
saved_prefix = self._prefix
self._prefix = []
self.add(node.value, '::', node.attr)
self._prefix = saved_prefix
def _ast_binop(self, node):
"""Add an AST BinOp in output."""
if isinstance(node.op, ast.Add) and \
(not self.is_number(node.left) or
not self.is_number(node.right)):
str_op = '.'
else:
str_op = self.binop[node.op.__class__.__name__]
self.add(
(self.prefix, '$'),
node.left,
' %s ' % str_op,
node.right,
(self.prefix, None),
)
def _ast_call(self, node):
"""Add an AST Call in output."""
self.add(
node.func,
'(',
(self.prefix, '$'),
self.make_list(node.args),
(self.prefix, None),
')',
)
def _ast_compare(self, node):
"""Add an AST Compare in output."""
self.add(node.left)
for operator, comparator in zip(node.ops, node.comparators):
if isinstance(operator, (ast.Eq, ast.NotEq)) and \
not self.is_number(node.left) and \
not self.is_bool(node.left) and \
not self.is_number(comparator) and \
not self.is_bool(comparator):
custom_cmpop = {
'Eq': 'eq',
'NotEq': 'ne',
}
else:
custom_cmpop = self.cmpop
self.add(
' %s ' % custom_cmpop[operator.__class__.__name__],
comparator,
)
def _ast_constant(self, node):
"""Add an AST Constant in output."""
if isinstance(node.value, str):
self.add('"%s"' % node.s.replace('$', '\\$').replace('@', '\\@'))
else:
self.add(repr(node.s))
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'{',
self.make_list([[key, ' => ', value]
for key, value in zip(node.keys, node.values)]),
'}',
)
def _ast_expr(self, node):
"""Add an AST Expr in output."""
if not isinstance(node.value, ast.Str): # ignore docstrings
self.add(
self.fill,
node.value,
';',
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
'sub %s' % node.name,
self.fill,
'{',
self.indent,
)
if node.args.args:
self.add(
self.fill,
'my (',
(self.prefix, '$'),
self.make_list(node.args.args),
(self.prefix, None),
') = @_;',
)
self.add(
node.body,
self.unindent,
self.fill,
'}',
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'if (',
(self.prefix, '$'),
node.test,
(self.prefix, None),
')',
self.fill,
'{',
self.indent,
node.body,
self.unindent,
self.fill,
'}',
)
if node.orelse:
self.add(
self.fill,
'else',
self.fill,
'{',
self.indent,
node.orelse,
self.unindent,
self.fill,
'}',
)
def _ast_pass(self, node):
"""Add an AST Pass in output."""
pass
def _ast_return(self, node):
"""Add an AST Return in output."""
self.fill('return')
if node.value:
self.add(
' ',
(self.prefix,
'%' if isinstance(node.value, ast.Dict) else '$'),
node.value,
(self.prefix, None),
';',
)
def _ast_str(self, node):
"""Add an AST Str in output."""
# note: deprecated since Python 3.8, replaced by ast.Constant
self.add('"%s"' % node.s.replace('$', '\\$').replace('@', '\\@'))
class UnparseRuby(UnparsePython):
"""
Unparse AST to generate Ruby script code.
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def _ast_attribute(self, node):
"""Add an AST Attribute in output."""
self.add(
node.value,
'::' if node.attr.startswith('WEECHAT_') else '.',
node.attr,
)
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'Hash[',
self.make_list([[key, ' => ', value]
for key, value in zip(node.keys, node.values)]),
']',
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
'def %s' % node.name,
)
if node.args.args:
self.add(
'(',
self.make_list(node.args.args),
')',
)
self.add(
self.indent,
node.body,
self.unindent,
self.fill,
'end',
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'if ',
node.test,
self.indent,
node.body,
self.unindent,
)
if node.orelse:
self.add(
self.fill,
'else',
self.indent,
node.orelse,
self.unindent,
self.fill,
'end',
)
def _ast_pass(self, node):
"""Add an AST Pass in output."""
pass
def _ast_str(self, node):
"""Add an AST Str in output."""
# note: deprecated since Python 3.8, replaced by ast.Constant
self.add('"%s"' % node.s)
class UnparseLua(UnparsePython):
"""
Unparse AST to generate Lua script code.
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def __init__(self, *args, **kwargs):
super(UnparseLua, self).__init__(*args, **kwargs)
self.cmpop = {
'Eq': '==',
'NotEq': '~=',
'Lt': '<',
'LtE': '<=',
'Gt': '>',
'GtE': '>=',
}
def _ast_binop(self, node):
"""Add an AST BinOp in output."""
if isinstance(node.op, ast.Add) and \
(not self.is_number(node.left) or
not self.is_number(node.right)):
str_op = '..'
else:
str_op = self.binop[node.op.__class__.__name__]
self.add(
node.left,
' %s ' % str_op,
node.right,
)
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'{',
self.make_list([
['[', key, ']', '=', value]
for key, value in zip(node.keys, node.values)]),
'}',
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
'function %s' % node.name,
)
self.add(
'(',
self.make_list(node.args.args),
')',
self.indent,
node.body,
self.unindent,
self.fill,
'end',
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'if ',
node.test,
' then',
self.indent,
node.body,
self.unindent,
)
if node.orelse:
self.add(
self.fill,
'else',
self.indent,
node.orelse,
self.unindent,
self.fill,
'end',
)
def _ast_pass(self, node):
"""Add an AST Pass in output."""
pass
class UnparseTcl(UnparsePython):
"""
Unparse AST to generate Tcl script code.
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def __init__(self, *args, **kwargs):
super(UnparseTcl, self).__init__(*args, **kwargs)
self._call = 0
def _ast_assign(self, node):
"""Add an AST Assign in output."""
self.add(
self.fill,
'set ',
node.targets[0],
' ',
'[' if not isinstance(node.value, ast.Str) else '',
node.value,
']' if not isinstance(node.value, ast.Str) else '',
)
def _ast_attribute(self, node):
"""Add an AST Attribute in output."""
saved_prefix = self._prefix
self._prefix = []
if node.attr.startswith('WEECHAT_'):
self.add('$::')
self.add(node.value, '::', node.attr)
self._prefix = saved_prefix
def _ast_binop(self, node):
"""Add an AST BinOp in output."""
self.add(
'[join [list ',
(self.prefix, '$'),
node.left,
' ',
node.right,
(self.prefix, None),
'] ""]',
)
def _ast_call(self, node):
"""Add an AST Call in output."""
if self._call:
self.add('[')
self._call += 1
self.add(
node.func,
' ' if node.args else None,
(self.prefix, '$'),
self.make_list(node.args, sep=' '),
(self.prefix, None),
)
self._call -= 1
if self._call:
self.add(']')
def _ast_compare(self, node):
"""Add an AST Compare in output."""
self.prefix('$')
if self._call:
self.add('[expr {')
self.add(node.left)
for operator, comparator in zip(node.ops, node.comparators):
if isinstance(operator, (ast.Eq, ast.NotEq)) and \
not self.is_number(node.left) and \
not self.is_bool(node.left) and \
not self.is_number(comparator) and \
not self.is_bool(comparator):
custom_cmpop = {
'Eq': 'eq',
'NotEq': 'ne',
}
else:
custom_cmpop = self.cmpop
self.add(
' %s ' % custom_cmpop[operator.__class__.__name__],
comparator,
)
if self._call:
self.add('}]')
self.prefix(None)
def _ast_constant(self, node):
"""Add an AST Constant in output."""
if isinstance(node.value, str):
self.add('"%s"' % node.s.replace('$', '\\$'))
else:
self.add(repr(node.s))
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'[dict create ',
self.make_list([[key, ' ', value]
for key, value in zip(node.keys, node.values)],
sep=' '),
']',
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
'proc %s {' % node.name,
(self.make_list(node.args.args, sep=' ')
if node.args.args else None),
'} {',
self.indent,
node.body,
self.unindent,
self.fill,
'}',
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'if {',
(self.prefix, '$'),
node.test,
(self.prefix, None),
'} {',
self.indent,
node.body,
self.unindent,
self.fill,
'}',
)
if node.orelse:
self.add(
' else {',
self.indent,
node.orelse,
self.unindent,
self.fill,
'}',
)
def _ast_pass(self, node):
"""Add an AST Pass in output."""
pass
def _ast_return(self, node):
"""Add an AST Return in output."""
self.fill('return')
if node.value:
self.add(
' ',
(self.prefix, '$'),
node.value,
(self.prefix, None),
)
def _ast_str(self, node):
"""Add an AST Str in output."""
# note: deprecated since Python 3.8, replaced by ast.Constant
self.add('"%s"' % node.s.replace('$', '\\$'))
class UnparseGuile(UnparsePython):
"""
Unparse AST to generate Guile script code.
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def __init__(self, *args, **kwargs):
super(UnparseGuile, self).__init__(*args, **kwargs)
self.cmpop = {
'Eq': '=',
'NotEq': '<>',
'Lt': '<',
'LtE': '<=',
'Gt': '>',
'GtE': '>=',
}
self._call = 0
self._let = 0
def _ast_assign(self, node):
"""Add an AST Assign in output."""
self.add(
self.fill,
'(let ((',
node.targets[0],
' ',
node.value,
'))',
self.indent,
self.fill,
'(begin',
self.indent,
)
self._let += 1
def _ast_attribute(self, node):
"""Add an AST Attribute in output."""
self.add(node.value, ':', node.attr)
def _ast_binop(self, node):
"""Add an AST BinOp in output."""
if isinstance(node.op, ast.Add) and \
(isinstance(node.left, (ast.Name, ast.Str)) or
isinstance(node.right, (ast.Name, ast.Str))):
self.add(
'(string-append ',
node.left,
' ',
node.right,
')',
)
else:
self.add(
node.left,
' %s ' % self.binop[node.op.__class__.__name__],
node.right,
)
def _ast_call(self, node):
"""Add an AST Call in output."""
self._call += 1
self.add(
'(',
node.func,
' ' if node.args else None,
self.make_list(node.args, sep=' '),
')',
)
self._call -= 1
def _ast_compare(self, node):
"""Add an AST Compare in output."""
for operator, comparator in zip(node.ops, node.comparators):
if isinstance(operator, (ast.Eq, ast.NotEq)) and \
not self.is_number(node.left) and \
not self.is_bool(node.left) and \
not self.is_number(comparator) and \
not self.is_bool(comparator):
prefix = 'string'
else:
prefix = ''
self.add(
'(%s%s ' % (prefix, self.cmpop[operator.__class__.__name__]),
node.left,
' ',
comparator,
')',
)
def _ast_constant(self, node):
"""Add an AST Constant in output."""
if isinstance(node.s, str):
self.add('"%s"' % node.s)
else:
self.add(repr(node.s))
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'\'(',
self.make_list([['(', key, ' ', value, ')']
for key, value in zip(node.keys, node.values)],
sep=' '),
')',
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
'(define (%s' % node.name,
' ' if node.args.args else None,
(self.make_list(node.args.args, sep=' ')
if node.args.args else None),
')',
self.indent,
node.body,
)
while self._let > 0:
self.add(
self.unindent,
self.fill,
')',
self.unindent,
self.fill,
')',
)
self._let -= 1
self.add(
self.unindent,
self.fill,
')',
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'(if '
'' if isinstance(node.test, ast.Name) else '(',
node.test,
'' if isinstance(node.test, ast.Name) else ')',
self.indent,
self.fill,
'(begin',
self.indent,
node.body,
self.unindent,
self.fill,
')',
self.unindent,
)
if node.orelse:
self.add(
self.indent,
self.fill,
'(begin',
self.indent,
node.orelse,
self.unindent,
self.fill,
')',
self.unindent,
)
self.add(self.fill, ')')
def _ast_pass(self, node):
"""Add an AST Pass in output."""
pass
def _ast_return(self, node):
"""Add an AST Return in output."""
if node.value:
self.add(self.fill, node.value)
def _ast_str(self, node):
"""Add an AST Str in output."""
# note: deprecated since Python 3.8, replaced by ast.Constant
self.add('"%s"' % node.s)
class UnparseJavascript(UnparsePython):
"""
Unparse AST to generate Javascript script code.
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'{',
self.make_list([[key, ': ', value]
for key, value in zip(node.keys, node.values)]),
'}',
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
'function %s(' % node.name,
self.make_list(node.args.args),
') {',
self.indent,
node.body,
self.unindent,
self.fill,
'}',
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'if (',
node.test,
') {',
self.indent,
node.body,
self.unindent,
self.fill,
'}',
)
if node.orelse:
self.add(
' else {',
self.indent,
node.orelse,
self.unindent,
self.fill,
'}',
)
def _ast_pass(self, node):
"""Add an AST Pass in output."""
pass
class UnparsePhp(UnparsePython):
"""
Unparse AST to generate PHP script code.
Note: only part of AST types are supported (just the types used by
the script to test WeeChat scripting API).
"""
__lineno__ = inspect.currentframe().f_lineno
def _ast_assign(self, node):
"""Add an AST Assign in output."""
self.add(
self.fill,
(self.prefix, '$'),
[[target, ' = '] for target in node.targets],
(self.prefix, None),
node.value,
';',
)
def _ast_attribute(self, node):
"""Add an AST Attribute in output."""
saved_prefix = self._prefix
self._prefix = []
if not node.attr.startswith('WEECHAT_'):
self.add(node.value, '_')
self.add(node.attr)
self._prefix = saved_prefix
def _ast_binop(self, node):
"""Add an AST BinOp in output."""
if isinstance(node.op, ast.Add) and \
(isinstance(node.left, (ast.Name, ast.Str)) or
isinstance(node.right, (ast.Name, ast.Str))):
str_op = '.'
else:
str_op = self.binop[node.op.__class__.__name__]
self.add(
(self.prefix, '$'),
node.left,
' %s ' % str_op,
node.right,
(self.prefix, None),
)
def _ast_call(self, node):
"""Add an AST Call in output."""
self.add(
node.func,
'(',
(self.prefix, '$'),
self.make_list(node.args),
(self.prefix, None),
')',
)
def _ast_constant(self, node):
"""Add an AST Constant in output."""
if isinstance(node.s, str):
self.add('"%s"' % node.s.replace('$', '\\$'))
else:
self.add(repr(node.s))
def _ast_dict(self, node):
"""Add an AST Dict in output."""
self.add(
'array(',
self.make_list([[key, ' => ', value]
for key, value in zip(node.keys, node.values)]),
')',
)
def _ast_expr(self, node):
"""Add an AST Expr in output."""
if not isinstance(node.value, ast.Str): # ignore docstrings
self.add(
self.fill,
node.value,
';',
)
def _ast_functiondef(self, node):
"""Add an AST FunctionDef in output."""
self.add(
self.fill,
self.fill,
'function %s(' % node.name,
(self.prefix, '$'),
self.make_list(node.args.args),
(self.prefix, None),
')',
self.fill,
'{',
self.indent,
node.body,
self.unindent,
self.fill,
'}',
)
def _ast_if(self, node):
"""Add an AST If in output."""
self.add(
self.fill,
'if (',
(self.prefix, '$'),
node.test,
(self.prefix, None),
')',
self.fill,
'{',
self.indent,
node.body,
self.unindent,
self.fill,
'}',
)
if node.orelse:
self.add(
self.fill,
'else',
self.fill,
'{',
self.indent,
node.orelse,
self.unindent,
self.fill,
'}',
)
def _ast_pass(self, node):
"""Add an AST Pass in output."""
pass
def _ast_return(self, node):
"""Add an AST Return in output."""
self.fill('return')
if node.value:
self.add(
' ',
(self.prefix, '$'),
node.value,
(self.prefix, None),
';',
)
def _ast_str(self, node):
"""Add an AST Str in output."""
# note: deprecated since Python 3.8, replaced by ast.Constant
self.add('"%s"' % node.s.replace('$', '\\$'))
def get_languages():
"""Return a list of supported languages: ['python', 'perl', ...]."""
members = [
member
for member in inspect.getmembers(sys.modules[__name__],
predicate=inspect.isclass)
if inspect.isclass(member[1]) and member[0].startswith('Unparse')
]
languages = [
name[7:].lower()
for name, _ in sorted(members,
key=lambda member: member[1].__lineno__)
]
return languages
LANGUAGES = get_languages()
def get_parser():
"""Get parser arguments."""
all_languages = LANGUAGES + ['all']
default_language = LANGUAGES[0]
parser = argparse.ArgumentParser(
description=('Unparse Python code from stdin and generate code in '
'another language (to stdout).\n\n'
'The code is read from stdin and generated code is '
'written on stdout.'))
parser.add_argument(
'-l', '--language',
default=default_language,
choices=all_languages,
help='output language (default: %s)' % default_language)
return parser
def get_stdin():
"""
Return data from standard input.
If there is nothing in stdin, wait for data until ctrl-D (EOF)
is received.
"""
data = ''
inr = select.select([sys.stdin], [], [], 0)[0]
if not inr:
print('Enter the code to convert (Enter + ctrl+D to end)')
while True:
inr = select.select([sys.stdin], [], [], 0.1)[0]
if not inr:
continue
new_data = os.read(sys.stdin.fileno(), 4096)
if not new_data: # EOF?
break
data += new_data.decode('utf-8')
return data
def convert_to_language(code, language, prefix=''):
"""Convert Python code to a language."""
class_name = 'Unparse%s' % language.capitalize()
unparse_class = getattr(sys.modules[__name__], class_name)
if prefix:
print(prefix)
output = StringIO()
unparse_class(output=output).add(ast.parse(code))
print(output.getvalue().lstrip())
def convert(code, language):
"""Convert Python code to one or all languages."""
if language == 'all':
for lang in LANGUAGES:
convert_to_language(code, lang, '\n%s:' % lang)
else:
convert_to_language(code, language)
def main():
"""Main function."""
parser = get_parser()
args = parser.parse_args()
code = get_stdin()
if not code:
print('ERROR: missing input')
print()
parser.print_help()
sys.exit(1)
convert(code, args.language)
sys.exit(0)
if __name__ == '__main__':
main()