diff --git a/cobra/base.py b/cobra/base.py index e25b6e1..c23ed18 100644 --- a/cobra/base.py +++ b/cobra/base.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- +import argparse import ast +import functools +import io +import sys from . import ast as ecma_ast from . import compiler @@ -17,19 +21,83 @@ def parse(data:str) -> object: return ast.parse(data) -def translate(data:object) -> object: +def translate(data:object, **kwargs) -> object: """ Given a python ast tree, translate it to ecma ast. """ - return translator.TranslateVisitor().translate(data) + return translator.TranslateVisitor(**kwargs).translate(data) -def compile(data:str, normalize:bool=True) -> str: - if normalize: - data = utils.normalize(data) +def compile(data:str, translate_options=None, compile_options=None) -> str: + if translate_options is None: + translate_options = {} + if compile_options is None: + compile_options = {} + + # Normalize + data = utils.normalize(data) + + # Parse python to ast python_tree = parse(data) - ecma_tree = translate(python_tree) - return compiler.ECMAVisitor().visit(ecma_tree) + + # Translate python ast to js ast + ecma_tree = translate(python_tree, **translate_options) + + # Compile js ast to js string + return compiler.ECMAVisitor(**compile_options).visit(ecma_tree) + + +def _read_file(path:str): + with io.open(path, "rt") as f: + return f.read() + +def _compile_files(paths:list, join=False, translate_options=None, compile_options=None) -> str: + _compile = functools.partial(compile, translate_options=translate_options, + compile_options=compile_options) + if join: + return _compile("\n".join(_read_file(path) for path in paths)) + return "\n\n".join(_compile(_read_file(path)) for path in paths) + + +def main(): + parser = argparse.ArgumentParser(prog="cobra-script", + description="Python to Javascript translator.") + parser.add_argument("files", metavar="input.py", type=str, nargs="+", + help="A list of python files for translate.") + parser.add_argument("-g", "--debug", action="store_true", default=False, + help="Activate debug mode (only for developers).") + parser.add_argument("-w", "--warnings", action="store_true", default=False, + help="Show statick analizer warnings.") + parser.add_argument("-o", "--output", action="store", type=str, metavar="outputfile.js", + help="Set output file (by default is stdout).") + parser.add_argument("-b", "--bare", action="store_true", default=False, + help="Compile without a toplevel closure.") + parser.add_argument("-j", "--join", action="store_true", default=False, + help="Join python files before compile.") + parser.add_argument("--indent", action="store", type=int, default=4, + help="Set default output indentation level.") + parser.add_argument("--auto-camelcase", action="store_true", default=False, + dest="auto_camelcase", help="Convert all identifiers to camel case.") + + parsed = parser.parse_args() + + reader_join = True if parsed.join else False + translate_options = {"module_as_closure": not parsed.bare, + "debug": parsed.debug, + "auto_camelcase": parsed.auto_camelcase} + compile_options = {"indent_chars": int(parsed.indent/2)} + + compiled_data = _compile_files(parsed.files, join=reader_join, + translate_options=translate_options, + compile_options=compile_options) + + if parsed.output: + with io.open(parsed.output, "wt") as f: + print(compiled_data, file=f) + else: + print(compiled_data, file=sys.stdout) + + return 0 diff --git a/cobra/translator.py b/cobra/translator.py index f0f82d2..4550721 100644 --- a/cobra/translator.py +++ b/cobra/translator.py @@ -6,12 +6,13 @@ from .utils import GenericStack from .utils import LeveledStack from .utils import ScopeStack +from .utils import to_camel_case from . import ast as ecma_ast class TranslateVisitor(ast.NodeVisitor): - def __init__(self, module_as_closure=False, debug=True): + def __init__(self, module_as_closure=False, auto_camelcase=False, debug=True): super().__init__() self.level_stack = LeveledStack() @@ -22,6 +23,7 @@ def __init__(self, module_as_closure=False, debug=True): self.indentation = 0 self.meta_debug = debug + self.meta_auto_camelcase = auto_camelcase self.meta_module_as_closure = module_as_closure self.meta_global_object = None @@ -33,6 +35,11 @@ def print(self, *args, **kwargs): def translate(self, tree): return self.visit(tree, root=True) + def process_idf(self, identifier): + if self.meta_auto_camelcase: + identifier.value = to_camel_case(identifier.value) + return identifier + def visit(self, node, root=False): self.level_stack.inc_level() @@ -137,7 +144,7 @@ def _translate_FunctionDef(self, node, childs): if scope_var_statement: body_stmts = [scope_var_statement] + body_stmts - identifier = ecma_ast.Identifier(node.name) + identifier = self.process_idf(ecma_ast.Identifier(node.name)) func_expr = ecma_ast.FuncExpr(None, childs[0], body_stmts) var_decl = ecma_ast.VarDecl(identifier, func_expr) @@ -166,7 +173,7 @@ def _translate_Module(self, node, childs): global_stmt = None if self.meta_global_object: - global_idf = ecma_ast.Identifier(self.meta_global_object) + global_idf = self.process_idf(ecma_ast.Identifier(self.meta_global_object)) self.scope.set(self.meta_global_object, global_idf) global_assign = ecma_ast.Assign("=", global_idf, ecma_ast.Identifier("this")) global_stmt = ecma_ast.ExprStatement(global_assign) @@ -208,10 +215,12 @@ def _translate_arguments(self, node, childs): def _translate_Name(self, node, childs): if node.id == "None": return ecma_ast.Null(node.id) - return ecma_ast.Identifier(node.id) + + name = node.id + return self.process_idf(ecma_ast.Identifier(name)) def _translate_arg(self, node, childs): - return ecma_ast.Identifier(node.arg) + return self.process_idf(ecma_ast.Identifier(node.arg)) def _translate_Str(self, node, childs): return ecma_ast.String('"{}"'.format(node.s)) @@ -232,7 +241,7 @@ def _translate_Call(self, node, childs): def _translate_Attribute(self, node, childs): variable_identifier = childs[0] - attribute_access_identifier = ecma_ast.Identifier(node.attr) + attribute_access_identifier = self.process_idf(ecma_ast.Identifier(node.attr)) dotaccessor = ecma_ast.DotAccessor(variable_identifier, attribute_access_identifier) return dotaccessor @@ -279,7 +288,7 @@ def _translate_Dict(self, node, childs): values = childs[msize:] for key, value in zip(keys, values): - identifier = ecma_ast.Identifier(key.value) + identifier = self.process_idf(ecma_ast.Identifier(key.value)) assign_instance = ecma_ast.Assign(":", identifier, value) properties.append(assign_instance) @@ -318,7 +327,7 @@ def get_unique_identifier(self, prefix="ref"): for i in range(100000000): candidate = "{}_{}".format(prefix, i) if candidate not in self.scope: - identifier = ecma_ast.Identifier(candidate) + identifier = self.process_idf(ecma_ast.Identifier(candidate)) self.scope.set(candidate, identifier) return identifier @@ -398,7 +407,7 @@ def _translate_ClassDef(self, node, childs): main_container_func = ecma_ast.FuncExpr(None, None, [scope_var_statement] + body_stmts) main_container_func._parens = True main_function_call = ecma_ast.FunctionCall(main_container_func) - main_identifier = ecma_ast.Identifier(node.name) + main_identifier = self.process_idf(ecma_ast.Identifier(node.name)) main_assign = ecma_ast.Assign("=", main_identifier, main_function_call) main_expr = ecma_ast.ExprStatement(main_assign) diff --git a/cobra/utils.py b/cobra/utils.py index a556545..2e15cba 100644 --- a/cobra/utils.py +++ b/cobra/utils.py @@ -10,6 +10,13 @@ def normalize(data:str): return textwrap.dedent(data).strip() +def to_camel_case(snake_str): + components = snake_str.split('_') + # We capitalize the first letter of each component except the first one + # with the 'title' method and join them together. + return components[0] + "".join(x.title() for x in components[1:]) + + class GenericStack(object): def __init__(self): self._data = [] diff --git a/cs.py b/cs.py index 65fac67..a37389c 100755 --- a/cs.py +++ b/cs.py @@ -2,20 +2,7 @@ # -*- coding: utf-8 -*- import sys -import io - -from cobra.base import compile - -def main(filename): - with io.open(filename, "rt") as f: - text = f.read() - r = compile(text) - print(r) - return 0 - +from cobra.base import main if __name__ == "__main__": - if len(sys.argv) != 2: - raise RuntimeError("Invalid parameters") - - sys.exit(main(sys.argv[1])) + sys.exit(main())