from pyprotobuf.commentresolver import CommentResolver
from pyprotobuf.dependencysorter import DependencySorter
from pyprotobuf.extensionsresolver import ExtensionsResolver
from pyprotobuf.nameresolver import NameResolver
from pyprotobuf.optionresolver import OptionResolver
from pyprotobuf.parser import ProtoParser
import copy
import logging
import os
import pyprotobuf.nodes as nodes
logger = logging.getLogger('pyprotobuf.Compiler')
class CompilerInput(object):
filename = None
ast = None
def __init__(self, filename):
self.filename = filename
def get_source(self):
return open(self.filename, 'r').read()
class StringCompilerInput(CompilerInput):
def __init__(self, filename, data):
super(StringCompilerInput, self).__init__(filename)
self.data = data
def get_source(self):
return self.data
[docs]class Compiler(object):
logger = logging.getLogger('pyprotobuf.Compiler')
passes = [
OptionResolver,
NameResolver,
ExtensionsResolver,
DependencySorter,
CommentResolver
]
def __init__(self):
self.parser = ProtoParser()
self.import_paths = [os.getcwd(), os.path.join(os.path.dirname(os.path.abspath(__file__)), 'proto')]
self.imports = {}
self.log_level = logging.INFO
self._inputs = []
def set_inputs(self, inputs):
"""
:type inputs: list(CompilerInput)
"""
self._inputs = inputs
def compile(self):
""" Return a RootNode containing all the PackageNode->FileNode
:rtype: pyprotobuf.nodes.RootNode
"""
#
self.root = nodes.RootNode()
self.traversal_path = []
for input in self._inputs:
input.ast = file_node = self._precompile_file(input.get_source(), input.filename)
self._add_file_to_package(file_node)
for compiler_pass_class in self.passes:
compiler_pass = compiler_pass_class(self)
compiler_pass.set_log_level(self.log_level)
compiler_pass.process(self.root)
return self.root
def _add_file_to_package(self, filenode):
# resolve an add other packages before adding ours
self._resolve_imports(filenode)
package_name = filenode.package_name
package = self.root.get_package(package_name)
if not package:
package = nodes.Package()
package.name = package_name
self.root.add_child(package)
package.add_child(filenode)
def _precompile_file(self, string, filename):
file_node = self.parser.parse_string(string)
# so the generators know the path
file_node.filename = filename
return file_node
def _resolve_import_path(self, path, file_node=None):
"""
:param path: Path to resolve
:param file_node: File node to resolve relative paths from.
"""
if path.startswith('/'):
return path
# search through each of the paths
# if specified, starting relative to the directory of the package
import_paths = copy.copy(self.import_paths)
if file_node is not None:
assert isinstance(file_node, nodes.FileNode)
import_paths.insert(0, os.path.dirname(file_node.filename))
split_path = path.split(os.pathsep)
for import_path in import_paths:
test_path = os.path.join(import_path, *split_path)
if os.path.exists(test_path):
return test_path
raise Exception("Could not find import %s in paths %s" % (path, import_paths))
def _resolve_imports(self, file_node):
"""
:type file_node: pyprotobuf.nodes.FileNode
"""
assert isinstance(file_node, nodes.FileNode)
# find any import nodes
for child in file_node.get_children_of_type(nodes.ImportNode):
path = self._resolve_import_path(child.value, file_node)
if path in self.imports:
file_node = self.imports[path]
else:
file_node = self._import_file(path)
self.logger.info('Importing %s from %s: %s %s', path, file_node.filename, file_node, file_node.children)
self.imports[path] = file_node
# attach the imported FileNode to the ImportNode
# XXX: unused
child.file_node = file_node
if path in self.traversal_path:
self.traversal_path.append(path)
raise Exception("Circular import %s" % self.traversal_path)
# visit other imports before we add ours
self.traversal_path.append(path)
self._resolve_imports(file_node)
self.traversal_path.pop()
self._add_file_to_package(file_node)
def _import_file(self, filename):
""" Return the FileNode node at path.
:rtype: pyprotobuf.nodes.FileNode
"""
if filename in self.traversal_path:
raise Exception("Circular import %s from %s" % (filename, self.traversal_path))
self.traversal_path.append(filename)
with open(filename, 'r') as f:
string = f.read()
filenode = self.parser.parse_string(string)
filenode.filename = filename
self._resolve_imports(filenode)
self.traversal_path.pop()
return filenode