Skip to content

Commit

Permalink
Add commandline parser and autocamelcase option.
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrey Antukh committed Dec 18, 2013
1 parent 26e9d5f commit e2d549b
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 31 deletions.
82 changes: 75 additions & 7 deletions cobra/base.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
27 changes: 18 additions & 9 deletions cobra/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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

Expand All @@ -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()

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand All @@ -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

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
7 changes: 7 additions & 0 deletions cobra/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
17 changes: 2 additions & 15 deletions cs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

0 comments on commit e2d549b

Please sign in to comment.