#!/usr/bin/env python
# Copyright (c) 2010 ActiveState Software Inc.
# See LICENSE.txt for license details.

"""Python binary file scanner for CodeIntel"""

import os
import types
import itertools
import imp
import subprocess
import sys
import cStringIO as io
import optparse

from process import ProcessOpen


SCAN_PROCESS_TIMEOUT = 2.0

class BinaryScanError(Exception): pass

class TimedPopen(ProcessOpen):
    def wait(self):
        return super(TimedPopen, self).wait(SCAN_PROCESS_TIMEOUT)


def safe_scan(path, python):
    """
    Performs a "safe" out-of-process scan.
    It is more safe because if the module being scanned runs amuck,
    it will not ruin the main Komodo interpreter.
    
        "path"   - is a path to a binary module
        "python" - an absolute path to the interpreter to run the scanner
    
    Returns a CIX 2.0 string.
    
    In case of errors raises a BinaryScanError.
    """
    # indirectly calls "_main" defined below
    
    # this is needed to avoid picking up a stale .pyc file.
    myself = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                          "pybinary.py")
    
    proc = TimedPopen(cmd="%s %s %s" % (python, myself, path),
                      env=dict(PYTHONPATH=os.pathsep.join(sys.path)))
    
    out, err = proc.communicate()
    if err:
        raise BinaryScanError(err)
    
    return out
    

def scan(path):
    """
    Performs an in-process binary module scan. That means the module is
    loaded (imported) into the current Python interpreter.
    
        "path" - a path to a binary module to scan
    
    Returns a CIX 2.0 XML string.
    """
    
    from gencix.python import gencixcore as gencix
    
    name,_ = os.path.splitext(os.path.basename(path))
    dir = os.path.dirname(path)

    root = gencix.Element('codeintel', version='2.0', name=name)
    
    gencix.docmodule(name, root, usefile=True, dir=dir)
    gencix.perform_smart_analysis(root)
    gencix.prettify(root)
    
    tree = gencix.ElementTree(root)
    
    stream = io.StringIO()
    try:
        stream.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        tree.write(stream)
        return stream.getvalue()
    finally:
        stream.close()
    

def _main(argv):
    parser = optparse.OptionParser(usage="%prog mdoulepath")
    (options, args) = parser.parse_args(args=argv)
    if len(args) != 1:
        parser.error("Incorrect number of args")
    
    mod_path = os.path.abspath(args[0])
    if not os.path.isfile(mod_path):
        parser.error("'%s' is not a file" % mod_path)
    
    cix = scan(mod_path)
    print cix


if __name__ == '__main__':
    _main(sys.argv[1:])

