def getReadersFromUrls(*sourceUrls, **options):
    readers = []
    for sourceUrl in sourceUrls:
        mibSource = urlparse.urlparse(sourceUrl)

        if sys.version_info[0:2] < (2, 5):
            class ParseResult(tuple):
                pass

            mibSource = ParseResult(mibSource)

            for k, v in zip(('scheme', 'netloc', 'path', 'params',
                             'query', 'fragment', 'username', 'password',
                             'hostname', 'port'), mibSource + ('', '', '', None)):
                if k == 'scheme':
                    if not mibSource[0] or mibSource[0] == 'file':
                        if mibSource[2].endswith('.zip') or mibSource[2].endswith('.ZIP'):
                            v = 'zip'

                setattr(mibSource, k, v)

        if mibSource.scheme in ('', 'file', 'zip'):
            scheme = mibSource.scheme
            filePath = url2pathname(mibSource.path)
            if scheme != 'file' and (filePath.endswith('.zip') or
                                     filePath.endswith('.ZIP')):
                scheme = 'zip'

            else:
                scheme = 'file'

            if scheme == 'file':
                readers.append(FileReader(filePath).setOptions(**options))
            else:
                readers.append(ZipReader(filePath).setOptions(**options))

        elif mibSource.scheme in ('http', 'https'):
            readers.append(HttpReader(mibSource.hostname or mibSource.netloc, mibSource.port or 80, mibSource.path,
                                      ssl=mibSource.scheme == 'https').setOptions(**options))

        elif mibSource.scheme in ('ftp', 'sftp'):
            readers.append(
                FtpReader(mibSource.hostname or mibSource.netloc, mibSource.path, ssl=mibSource.scheme == 'sftp',
                          port=mibSource.port or 21, user=mibSource.username or 'anonymous',
                          password=mibSource.password or 'anonymous@').setOptions(**options))

        else:
            raise error.PySmiError('Unsupported URL scheme %s' % sourceUrl)

    return readers class PyFileSearcher(AbstractSearcher):
    """Figures out if given Python file (source or bytecode) exists at given
       location.
    """
    def __init__(self, path):
        """Create an instance of *PyFileSearcher* bound to specific directory.

           Args:
             path (str): path to local directory
        """
        self._path = os.path.normpath(decode(path))

    def __str__(self):
        return '%s{"%s"}' % (self.__class__.__name__, self._path)

    def fileExists(self, mibname, mtime, rebuild=False):
        if rebuild:
            debug.logger & debug.flagSearcher and debug.logger('pretend %s is very old' % mibname)
            return

        mibname = decode(mibname)
        pyfile = os.path.join(self._path, mibname)

        for pySfx in BYTECODE_SUFFIXES:
            f = pyfile + pySfx

            if not os.path.exists(f) or not os.path.isfile(f):
                debug.logger & debug.flagSearcher and debug.logger('%s not present or not a file' % f)
                continue

            try:
                fp = open(f, 'rb')
                pyData = fp.read(8)
                fp.close()

            except IOError:
                raise error.PySmiSearcherError('failure opening compiled file %s: %s' % (f, sys.exc_info()[1]),
                                               searcher=self)
            if pyData[:4] == PY_MAGIC_NUMBER:
                pyData = pyData[4:]
                pyTime = struct.unpack('<L', pyData[:4])[0]
                debug.logger & debug.flagSearcher and debug.logger(
                    'found %s, mtime %s' % (f, time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(pyTime))))
                if pyTime >= mtime:
                    raise error.PySmiFileNotModifiedError()

                else:
                    raise error.PySmiFileNotFoundError('older file %s exists' % mibname, searcher=self)

            else:
                debug.logger & debug.flagSearcher and debug.logger('bad magic in %s' % f)
                continue

        for pySfx in SOURCE_SUFFIXES:
            f = pyfile + pySfx

            if not os.path.exists(f) or not os.path.isfile(f):
                debug.logger & debug.flagSearcher and debug.logger('%s not present or not a file' % f)
                continue

            try:
                pyTime = os.stat(f)[8]

            except OSError:
                raise error.PySmiSearcherError('failure opening compiled file %s: %s' % (f, sys.exc_info()[1]),
                                               searcher=self)

            debug.logger & debug.flagSearcher and debug.logger(
                'found %s, mtime %s' % (f, time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(pyTime))))

            if pyTime >= mtime:
                raise error.PySmiFileNotModifiedError()

        raise error.PySmiFileNotFoundError('no compiled file %s found' % mibname, searcher=self) class PyPackageSearcher(AbstractSearcher):
    """Figures out if given Python module (source or bytecode) exists in given
       Python package.

       Python package must be importable.
    """
    def __init__(self, package):
        """Create an instance of *PyPackageSearcher* bound to specific Python
           package.

           Args:
               package (str): name of the Python package to look up Python
                              modules at.
        """
        self._package = package
        self.__loader = None

    def __str__(self):
        return '%s{"%s"}' % (self.__class__.__name__, self._package)

    @staticmethod
    def _parseDosTime(dosdate, dostime):
        t = (((dosdate >> 9) & 0x7f) + 1980,  # year
             ((dosdate >> 5) & 0x0f),  # month
             dosdate & 0x1f,  # mday
             (dostime >> 11) & 0x1f,  # hour
             (dostime >> 5) & 0x3f,  # min
             (dostime & 0x1f) * 2,  # sec
             -1,  # wday
             -1,  # yday
             -1)  # dst
        return time.mktime(t)

    def fileExists(self, mibname, mtime, rebuild=False):
        if rebuild:
            debug.logger & debug.flagSearcher and debug.logger('pretend %s is very old' % mibname)
            return

        mibname = decode(mibname)

        try:
            p = __import__(self._package, globals(), locals(), ['__init__'])

            if hasattr(p, '__loader__') and hasattr(p.__loader__, '_files'):
                self.__loader = p.__loader__
                self._package = self._package.replace('.', os.sep)
                debug.logger & debug.flagSearcher and debug.logger(
                    '%s is an importable egg at %s' % (self._package, os.path.split(p.__file__)[0]))

            elif hasattr(p, '__file__'):
                debug.logger & debug.flagSearcher and debug.logger(
                    '%s is not an egg, trying it as a package directory' % self._package)
                return PyFileSearcher(os.path.split(p.__file__)[0]).fileExists(mibname, mtime, rebuild=rebuild)

            else:
                raise error.PySmiFileNotFoundError('%s is neither importable nor a file' % self._package, searcher=self)

        except ImportError:
            raise error.PySmiFileNotFoundError('%s is not importable, trying as a path' % self._package, searcher=self)

        for pySfx in BYTECODE_SUFFIXES:
            f = os.path.join(self._package, mibname.upper()) + pySfx

            if f not in self.__loader._files:
                debug.logger & debug.flagSearcher and debug.logger('%s is not in %s' % (f, self._package))
                continue

            pyData = self.__loader.get_data(f)
            if pyData[:4] == PY_MAGIC_NUMBER:
                pyData = pyData[4:]
                pyTime = struct.unpack('<L', pyData[:4])[0]
                debug.logger & debug.flagSearcher and debug.logger(
                    'found %s, mtime %s' % (f, time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(pyTime))))
                if pyTime >= mtime:
                    raise error.PySmiFileNotModifiedError()
                else:
                    raise error.PySmiFileNotFoundError('older file %s exists' % mibname, searcher=self)

            else:
                debug.logger & debug.flagSearcher and debug.logger('bad magic in %s' % f)
                continue

        for pySfx in SOURCE_SUFFIXES:

            f = os.path.join(self._package, mibname.upper()) + pySfx

            if f not in self.__loader._files:
                debug.logger & debug.flagSearcher and debug.logger('%s is not in %s' % (f, self._package))
                continue

            pyTime = self._parseDosTime(
                self.__loader._files[f][6],
                self.__loader._files[f][5]
            )

            debug.logger & debug.flagSearcher and debug.logger(
                'found %s, mtime %s' % (f, time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(pyTime))))
            if pyTime >= mtime:
                raise error.PySmiFileNotModifiedError()
            else:
                raise error.PySmiFileNotFoundError('older file %s exists' % mibname, searcher=self)

        raise error.PySmiFileNotFoundError('no file %s found' % mibname, searcher=self) class StubSearcher(AbstractSearcher):
    """Figures out if given MIB module is present in a fixed list of modules.
    """

    def __init__(self, *mibnames):
        """Create an instance of *StubSearcher* initialized with a fixed list
           or MIB modules names.

           Args:
               mibnames (str): blacklisted MIB names
        """
        self._mibnames = mibnames

    def __str__(self):
        return '%s' % self.__class__.__name__

    def fileExists(self, mibname, mtime, rebuild=False):
        if mibname in self._mibnames:
            debug.logger & debug.flagSearcher and debug.logger('pretend compiled %s exists and is very new' % mibname)
            raise error.PySmiFileNotModifiedError('compiled file %s is among %s' % (mibname, ', '.join(self._mibnames)),
                                                  searcher=self)

        raise error.PySmiFileNotFoundError('no compiled file %s found among %s' % (mibname, ', '.join(self._mibnames)),
                                           searcher=self) class AnyFileSearcher(AbstractSearcher):
    """Figures out if given file exists at given location.
    """
    exts = []

    def __init__(self, path):
        """Create an instance of *AnyFileSearcher* bound to specific directory.

           Args:
             path (str): path to local directory
        """
        self._path = os.path.normpath(decode(path))

    def __str__(self):
        return '%s{"%s"}' % (self.__class__.__name__, self._path)

    def fileExists(self, mibname, mtime, rebuild=False):
        if rebuild:
            debug.logger & debug.flagSearcher and debug.logger('pretend %s is very old' % mibname)
            return

        mibname = decode(mibname)
        basename = os.path.join(self._path, mibname)

        for sfx in self.exts:
            f = basename + sfx
            if not os.path.exists(f) or not os.path.isfile(f):
                debug.logger & debug.flagSearcher and debug.logger('%s not present or not a file' % f)
                continue

            try:
                fileTime = os.stat(f)[8]

            except OSError:
                raise error.PySmiSearcherError('failure opening compiled file %s: %s' % (f, sys.exc_info()[1]),
                                               searcher=self)

            debug.logger & debug.flagSearcher and debug.logger(
                'found %s, mtime %s' % (f, time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(fileTime))))

            if fileTime >= mtime:
                raise error.PySmiFileNotModifiedError()

        raise error.PySmiFileNotFoundError('no compiled file %s found' % mibname, searcher=self) class PyFileBorrower(AbstractBorrower):
    """Create PySNMP MIB file borrowing object"""
    exts = SOURCE_SUFFIXES class AnyFileBorrower(AbstractBorrower):
    """Create arbitrary MIB file borrowing object""" class FileWriter(AbstractWriter):
    """Stores transformed MIB modules in files at specified location.

       User is expected to pass *FileReader* class instance to
       *MibCompiler* on instantiation. The rest is internal to *MibCompiler*.
    """
    suffix = ''

    def __init__(self, path):
        """Creates an instance of *FileReader* class.

           Args:
               path: writable directory to store created files
        """
        self._path = decode(os.path.normpath(path))

    def __str__(self):
        return '%s{"%s"}' % (self.__class__.__name__, self._path)

    def getData(self, mibname, dryRun=False):
        filename = os.path.join(self._path, decode(mibname)) + self.suffix

        f = None

        try:
            f = open(filename)
            data = f.read()
            f.close()
            return data

        except (OSError, IOError, UnicodeEncodeError):
            if f:
                f.close()
            return ''

    def putData(self, mibname, data, comments=(), dryRun=False):
        if dryRun:
            debug.logger & debug.flagWriter and debug.logger('dry run mode')
            return

        if not os.path.exists(self._path):
            try:
                os.makedirs(self._path)

            except OSError:
                raise error.PySmiWriterError(
                    'failure creating destination directory %s: %s' % (self._path, sys.exc_info()[1]), writer=self)

        if comments:
            data = '#\n' + ''.join(['# %s\n' % x for x in comments]) + '#\n' + data

        filename = os.path.join(self._path, decode(mibname)) + self.suffix

        tfile = None

        try:
            fd, tfile = tempfile.mkstemp(dir=self._path)
            os.write(fd, encode(data))
            os.close(fd)
            os.rename(tfile, filename)

        except (OSError, IOError, UnicodeEncodeError):
            exc = sys.exc_info()
            if tfile:
                try:
                    os.unlink(tfile)

                except OSError:
                    pass

            raise error.PySmiWriterError('failure writing file %s: %s' % (filename, exc[1]), file=filename, writer=self)

        debug.logger & debug.flagWriter and debug.logger('%s stored in %s' % (mibname, filename)) class PyFileWriter(AbstractWriter):
    """Stores transformed MIB modules as Python files at specified location.

       User is expected to pass *PyFileWriter* class instance to
       *MibCompiler* on instantiation. The rest is internal to *MibCompiler*.
    """
    pyCompile = True
    pyOptimizationLevel = -1

    def __init__(self, path):
        """Creates an instance of *PyFileWriter* class.

           Args:
               path: writable directory to store Python modules
        """
        self._path = decode(os.path.normpath(path))

    def __str__(self):
        return '%s{"%s"}' % (self.__class__.__name__, self._path)

    def putData(self, mibname, data, comments=(), dryRun=False):
        if dryRun:
            debug.logger & debug.flagWriter and debug.logger('dry run mode')
            return

        if not os.path.exists(self._path):
            try:
                os.makedirs(self._path)

            except OSError:
                raise error.PySmiWriterError(
                    'failure creating destination directory %s: %s' % (self._path, sys.exc_info()[1]), writer=self)

        if comments:
            data = '#\n' + ''.join(['# %s\n' % x for x in comments]) + '#\n' + data

        pyfile = os.path.join(self._path, decode(mibname))
        pyfile += SOURCE_SUFFIXES[0]

        tfile = None

        try:
            fd, tfile = tempfile.mkstemp(dir=self._path)
            os.write(fd, encode(data))
            os.close(fd)
            os.rename(tfile, pyfile)

        except (OSError, IOError, UnicodeEncodeError):
            exc = sys.exc_info()
            if tfile and os.access(tfile, os.F_OK):
                os.unlink(tfile)

            raise error.PySmiWriterError('failure writing file %s: %s' % (pyfile, exc[1]), file=pyfile, writer=self)

        debug.logger & debug.flagWriter and debug.logger('created file %s' % pyfile)

        if self.pyCompile:
            try:
                if sys.version_info[0:2] > (3, 1):
                    # noinspection PyArgumentList
                    py_compile.compile(pyfile, doraise=True, optimize=self.pyOptimizationLevel)

                else:
                    py_compile.compile(pyfile, doraise=True)

            except (SyntaxError, py_compile.PyCompileError):
                pass  # XXX

            except Exception:
                if pyfile and os.access(pyfile, os.F_OK):
                    os.unlink(pyfile)

                raise error.PySmiWriterError('failure compiling %s: %s' % (pyfile, sys.exc_info()[1]), file=mibname, writer=self)

        debug.logger & debug.flagWriter and debug.logger('%s stored' % mibname)

    def getData(self, filename):
        return '' class CallbackWriter(AbstractWriter):
    """Invokes user-specified callable and passes transformed
       MIB module to it.

       Note: user callable object signature must be as follows

       .. function:: cbFun(mibname, contents, cbCtx)

    """

    def __init__(self, cbFun, cbCtx=None):
        """Creates an instance of *CallbackWriter* class.

        Args:
            cbFun (callable): user-supplied callable
        Keyword Args:
            cbCtx: user-supplied object passed intact to user callback
        """
        self._cbFun = cbFun
        self._cbCtx = cbCtx

    def __str__(self):
        return '%s{"%s"}' % (self.__class__.__name__, self._cbFun)

    def putData(self, mibname, data, comments=(), dryRun=False):
        if dryRun:
            debug.logger & debug.flagWriter and debug.logger('dry run mode')
            return

        try:
            self._cbFun(mibname, data, self._cbCtx)

        except Exception:
            raise error.PySmiWriterError(
                'user callback %s failure writing %s: %s' % (self._cbFun, mibname, sys.exc_info()[1]), writer=self)

        debug.logger & debug.flagWriter and debug.logger('user callback for %s succeeded' % mibname)

    def getData(self, filename):
        return '' class PySnmpCodeGen(IntermediateCodeGen):
    """Turns MIB AST into pysnmp Python code.

    Builds Python module representing MIB module in terms of pysnmp objects
    from MIB supplied in form of an Abstract Syntax Tree on input.

    Instance of this class is supposed to be passed to *MibCompiler*,
    the rest is internal to *MibCompiler*.
    """

    defaultMibPackages = ('pysnmp.smi.mibs', 'pysnmp_mibs')

    TEMPLATE_NAME = 'pysnmp/mib-definitions.j2'

    SMI_OBJECTS = {
        'MODULE-IDENTITY': ['ModuleIdentity'],
        'OBJECT-TYPE': ['MibScalar', 'MibTable', 'MibTableRow', 'MibTableColumn'],
        'NOTIFICATION-TYPE': ['NotificationType'],
        'TEXTUAL-CONVENTION': ['TextualConvention'],
        'MODULE-COMPLIANCE': ['ModuleCompliance'],
        'OBJECT-GROUP': ['ObjectGroup'],
        'NOTIFICATION-GROUP': ['NotificationGroup'],
        'AGENT-CAPABILITIES': ['AgentCapabilities'],
        'OBJECT-IDENTITY': ['ObjectIdentity'],
        'TRAP-TYPE': ['NotificationType'],
        'BITS': ['Bits']
    }

    SMI_TYPES = {
        'COUNTER32': 'Counter32',
        'COUNTER64': 'Counter64',
        'GAUGE32': 'Gauge32',
        'INTEGER': 'Integer32',  # XXX
        'INTEGER32': 'Integer32',
        'IPADDRESS': 'IpAddress',
        'NETWORKADDRESS': 'IpAddress',
        'OBJECT IDENTIFIER': 'ObjectIdentifier',
        'OCTET STRING': 'OctetString',
        'OPAQUE': 'Opaque',
        'TIMETICKS': 'TimeTicks',
        'UNSIGNED32': 'Unsigned32',
        'Counter': 'Counter32',
        'Gauge': 'Gauge32',
        'NetworkAddress': 'IpAddress',  # RFC1065-SMI, RFC1155-SMI -> SNMPv2-SMI
        'nullSpecific': 'zeroDotZero',  # RFC1158-MIB -> SNMPv2-SMI
        'ipRoutingTable': 'ipRouteTable',  # RFC1158-MIB -> RFC1213-MIB
        'snmpEnableAuthTraps': 'snmpEnableAuthenTraps'  # RFC1158-MIB -> SNMPv2-MIB
    }

    # never compile these, they either:
    # - define MACROs (implementation supplies them)
    # - or carry conflicting OIDs (so that all IMPORT's of them will be rewritten)
    # - or have manual fixes
    # - or import base ASN.1 types from implementation-specific MIBs
    fakeMibs = ('ASN1',
                'ASN1-ENUMERATION',
                'ASN1-REFINEMENT')
    baseMibs = ('PYSNMP-USM-MIB',
                'SNMP-FRAMEWORK-MIB',
                'SNMP-TARGET-MIB',
                'TRANSPORT-ADDRESS-MIB',
                'INET-ADDRESS-MIB') + IntermediateCodeGen.baseMibs

    def genCode(self, ast, symbolTable, **kwargs):
        mibInfo, context = IntermediateCodeGen.genCode(self, ast, symbolTable, **kwargs)

        # Adapt intermediate context to pysnmp template requirements

        # Translate SMI objects names in IMPORT

        imports = OrderedDict()

        for module, symbols in context.get('imports', {}).items():
            if not isinstance(symbols, list):
                continue

            imports[module] = []

            for symbol in symbols:
                if symbol in self.SMI_OBJECTS:
                    imports[module].extend(self.SMI_OBJECTS[symbol])
                else:
                    imports[module].append(symbol)

        context['imports'] = imports

        # Turn string OIDs into tuples which is native to pysnmp
        # TODO: we should make intermediate format producing tuples

        def translateOids(dct):
            for key, value in tuple(dct.items()):
                if isinstance(value, dict):
                    translateOids(value)
                elif key == 'oid':
                    dct[key] = tuple(int(x) for x in value.split('.'))

        translateOids(context)

        # Translate SMI types into pysnmp class names

        # Sort Managed Objects by OID
        objects = OrderedDict()

        for symbol, definition in sorted(
                context.items(), key=lambda x: x[1].get('oid', ())):
            objects[symbol] = definition

        context = objects

        # Render Python code

        searchPath = [os.path.join(os.path.dirname(__file__), 'templates')]

        # TODO: add unit test on custom template

        dstTemplate = kwargs.get('dstTemplate')
        if dstTemplate:
            searchPath.insert(0, os.path.dirname(os.path.abspath(dstTemplate)))

        env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchPath),
                                 trim_blocks=True, lstrip_blocks=True)

        env.filters['capfirst'] = jfilters.capfirst

        try:
            tmpl = env.get_template(dstTemplate or self.TEMPLATE_NAME)
            text = tmpl.render(mib=context)

        except jinja2.exceptions.TemplateError:
            err = sys.exc_info()[1]
            raise error.PySmiCodegenError('Jinja template rendering error: %s' % err)

        debug.logger & debug.flagCodegen and debug.logger(
            'canonical MIB name %s (%s), imported MIB(s) %s, rendered from '
            '%s, Python code size %d bytes' % (
                mibInfo.name, mibInfo.identity,
                ','.join(mibInfo.imported) or '<none>',
                dstTemplate, len(text)))

        return mibInfo, text class JsonCodeGen(IntermediateCodeGen):
    """Turns MIB AST into JSON document.

    Builds JSON document representing MIB module supplied in form of
    an Abstract Syntax Tree on input.

    Instance of this class is supposed to be passed to *MibCompiler*,
    the rest is internal to *MibCompiler*.
    """
    TEMPLATE_NAME = 'jsondoc/base.j2'

    def genCode(self, ast, symbolTable, **kwargs):
        mibInfo, context = IntermediateCodeGen.genCode(self, ast, symbolTable, **kwargs)

        # TODO: reduce code duplication with the other codegens

        searchPath = os.path.join(os.path.dirname(__file__), 'templates')

        dstTemplate = kwargs.get('dstTemplate')
        if dstTemplate:
            searchPath.insert(0, os.path.dirname(os.path.abspath(dstTemplate)))

        env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchPath),
                                 trim_blocks=True, lstrip_blocks=True)

        env.filters['capfirst'] = jfilters.capfirst

        try:
            tmpl = env.get_template(dstTemplate or self.TEMPLATE_NAME)
            text = tmpl.render(mib=context)

        except jinja2.exceptions.TemplateError:
            err = sys.exc_info()[1]
            raise error.PySmiCodegenError('Jinja template rendering error: %s' % err)

        debug.logger & debug.flagCodegen and debug.logger(
            'canonical MIB name %s (%s), imported MIB(s) %s, rendered from '
            '%s, JSON document size %d bytes' % (
                mibInfo.name, mibInfo.identity,
                ','.join(mibInfo.imported) or '<none>',
                dstTemplate, len(text)))

        return mibInfo, text

    # TODO: move this to a template
    def genIndex(self, processed, **kwargs):
        outDict = {
            'meta': {},
            'identity': {},
            'enterprise': {},
            'compliance': {},
            'oids': {},
        }
        if kwargs.get('old_index_data'):
            try:
                outDict.update(
                    json.loads(kwargs['old_index_data'])
                )

            except Exception:
                raise error.PySmiCodegenError('Index load error: %s' % sys.exc_info()[1])

        def order(top):
            if isinstance(top, dict):
                new_top = OrderedDict()
                try:
                    # first try to sort keys as OIDs
                    for k in sorted(top, key=lambda x: [int(y) for y in x.split('.')]):
                        new_top[k] = order(top[k])

                except ValueError:
                    for k in sorted(top):
                        new_top[k] = order(top[k])

                return new_top
            elif isinstance(top, list):
                new_top = []
                for e in sorted(set(top)):
                    new_top.append(order(e))

                return new_top

            return top

        for module, status in processed.items():
            modData = outDict['identity']
            identity_oid = getattr(status, 'identity', None)
            if identity_oid:
                if identity_oid not in modData:
                    modData[identity_oid] = []

                modData[identity_oid].append(module)

            modData = outDict['enterprise']
            enterprise_oid = getattr(status, 'enterprise', None)
            if enterprise_oid:
                if enterprise_oid not in modData:
                    modData[enterprise_oid] = []

                modData[enterprise_oid].append(module)

            modData = outDict['compliance']
            compliance_oids = getattr(status, 'compliance', ())
            for compliance_oid in compliance_oids:
                if compliance_oid not in modData:
                    modData[compliance_oid] = []
                modData[compliance_oid].append(module)

            modData = outDict['oids']
            objects_oids = getattr(status, 'oids', ())
            for object_oid in objects_oids:
                if object_oid not in modData:
                    modData[object_oid] = []

                modData[object_oid].append(module)

            if modData:
                unique_prefixes = {}
                for oid in sorted(modData, key=lambda x: x.count('.')):
                    for oid_prefix, modules in unique_prefixes.items():
                        if oid.startswith(oid_prefix) and set(modules).issuperset(modData[oid]):
                            break
                    else:
                        unique_prefixes[oid] = modData[oid]

                outDict['oids'] = unique_prefixes

        if 'comments' in kwargs:
            outDict['meta']['comments'] = kwargs['comments']

        debug.logger & debug.flagCodegen and debug.logger(
            'OID->MIB index built, %s entries' % len(processed))

        return json.dumps(order(outDict), indent=2) class NullCodeGen(AbstractCodeGen):
    """Dummy code generation backend.

       Could be used for disabling code generation at *MibCompiler*.
    """

    def genCode(self, ast, symbolTable, **kwargs):
        debug.logger & debug.flagCodegen and debug.logger('%s invoked' % self.__class__.__name__)
        return MibInfo(oid=None, name='', imported=[]), ''

    def genIndex(self, mibsMap, **kwargs):
        return '' class MibCompiler(object):
    """Top-level, user-facing, composite MIB compiler object.

    MibCompiler implements high-level MIB transformation processing logic.
    It executes its actions by calling the following specialized objects:

      * *readers* - to acquire ASN.1 MIB data
      * *searchers* - to see if transformed MIB already exists and no processing is necessary
      * *parser* - to parse ASN.1 MIB into AST
      * *code generator* - to perform actual MIB transformation
      * *borrowers* - to fetch pre-transformed MIB if transformation is impossible
      * *writer* - to store transformed MIB data

    Required components must be passed to MibCompiler on instantiation. Those
    components are: *parser*, *codegenerator* and *writer*.

    Optional components could be set or modified at later phases of MibCompiler
    life. Unlike singular, required components, optional one can be present
    in sequences to address many possible sources of data. They are
    *readers*, *searchers* and *borrowers*.
    """
    indexFile = 'index'

    def __init__(self, parser, codegen, writer):
        """Creates an instance of *MibCompiler* class.

           Args:
               parser: ASN.1 MIB parser object
               codegen: MIB transformation object
               writer: transformed MIB storing object
        """
        self._parser = parser
        self._codegen = codegen
        self._symbolgen = SymtableCodeGen()
        self._writer = writer
        self._sources = []
        self._searchers = []
        self._borrowers = []

    def addSources(self, *sources):
        """Add more ASN.1 MIB source repositories.

        MibCompiler.compile will invoke each of configured source objects
        in order of their addition asking each to fetch MIB module specified
        by name.

        Args:
            sources: reader object(s)

        Returns:
            reference to itself (can be used for call chaining)

        """
        self._sources.extend(sources)

        debug.logger & debug.flagCompiler and debug.logger(
            'current MIB source(s): %s' % ', '.join([str(x) for x in self._sources]))

        return self

    def addSearchers(self, *searchers):
        """Add more transformed MIBs repositories.

        MibCompiler.compile will invoke each of configured searcher objects
        in order of their addition asking each if already transformed MIB
        module already exists and is more recent than specified.

        Args:
            searchers: searcher object(s)

        Returns:
            reference to itself (can be used for call chaining)

        """
        self._searchers.extend(searchers)

        debug.logger & debug.flagCompiler and debug.logger(
            'current compiled MIBs location(s): %s' % ', '.join([str(x) for x in self._searchers]))

        return self

    def addBorrowers(self, *borrowers):
        """Add more transformed MIBs repositories to borrow MIBs from.

        Whenever MibCompiler.compile encounters MIB module which neither of
        the *searchers* can find or fetched ASN.1 MIB module can not be
        parsed (due to syntax errors), these *borrowers* objects will be
        invoked in order of their addition asking each if already transformed
        MIB can be fetched (borrowed).

        Args:
            borrowers: borrower object(s)

        Returns:
            reference to itself (can be used for call chaining)

        """
        self._borrowers.extend(borrowers)

        debug.logger & debug.flagCompiler and debug.logger(
            'current MIB borrower(s): %s' % ', '.join([str(x) for x in self._borrowers]))

        return self

    def _get_system_info(self):

        try:
            platform_info = os.uname()

        except AttributeError:
            platform_info = ('?',) * 6

        try:
            user_info = getpwuid(os.getuid())

        except Exception:
            user_info = ('?',) * 7

        return platform_info, user_info

    def compile(self, *mibnames, **options):
        """Transform requested and possibly referred MIBs.

        The *compile* method should be invoked when *MibCompiler* object
        is operational meaning at least *sources* are specified.

        Once called with a MIB module name, *compile* will:

        * fetch ASN.1 MIB module with given name by calling *sources*
        * make sure no such transformed MIB already exists (with *searchers*)
        * parse ASN.1 MIB text with *parser*
        * perform actual MIB transformation into target format with *code generator*
        * may attempt to borrow pre-transformed MIB through *borrowers*
        * write transformed MIB through *writer*

        The above sequence will be performed for each MIB name given in
        *mibnames* and may be performed for all MIBs referred to from
        MIBs being processed.

        Args:
            mibnames: list of ASN.1 MIBs names
            options: options that affect the way PySMI components work

        Returns:
            A dictionary of MIB module names processed (keys) and *MibStatus*
            class instances (values)

        """
        processed = {}
        parsedMibs = {}
        failedMibs = {}
        borrowedMibs = {}
        builtMibs = {}
        symbolTableMap = {}
        mibsToParse = [x for x in mibnames]
        canonicalMibNames = {}

        while mibsToParse:
            mibname = mibsToParse.pop(0)

            if mibname in parsedMibs:
                debug.logger & debug.flagCompiler and debug.logger('MIB %s already parsed' % mibname)
                continue

            if mibname in failedMibs:
                debug.logger & debug.flagCompiler and debug.logger('MIB %s already failed' % mibname)
                continue

            for source in self._sources:
                debug.logger & debug.flagCompiler and debug.logger('trying source %s' % source)

                try:
                    fileInfo, fileData = source.getData(mibname)

                    for mibTree in self._parser.parse(fileData):
                        mibInfo, symbolTable = self._symbolgen.genCode(
                            mibTree, symbolTableMap
                        )

                        symbolTableMap[mibInfo.name] = symbolTable

                        parsedMibs[mibInfo.name] = fileInfo, mibInfo, mibTree

                        if mibname in failedMibs:
                            del failedMibs[mibname]

                        mibsToParse.extend(mibInfo.imported)

                        if fileInfo.name in mibnames:
                            if mibInfo.name not in canonicalMibNames:
                                canonicalMibNames[mibInfo.name] = []
                            canonicalMibNames[mibInfo.name].append(fileInfo.name)

                        debug.logger & debug.flagCompiler and debug.logger(
                            '%s (%s) read from %s, immediate dependencies: %s' % (
                                mibInfo.name, mibname, fileInfo.path, ', '.join(mibInfo.imported) or '<none>'))

                    break

                except error.PySmiReaderFileNotFoundError:
                    debug.logger & debug.flagCompiler and debug.logger('no %s found at %s' % (mibname, source))
                    continue

                except error.PySmiError:
                    exc_class, exc, tb = sys.exc_info()
                    exc.source = source
                    exc.mibname = mibname
                    exc.msg += ' at MIB %s' % mibname

                    debug.logger & debug.flagCompiler and debug.logger('%serror %s from %s' % (
                        options.get('ignoreErrors') and 'ignoring ' or 'failing on ', exc, source))

                    failedMibs[mibname] = exc

                    processed[mibname] = statusFailed.setOptions(error=exc)

            else:
                exc = error.PySmiError('MIB source %s not found' % mibname)
                exc.mibname = mibname
                debug.logger & debug.flagCompiler and debug.logger('no %s found everywhere' % mibname)

                if mibname not in failedMibs:
                    failedMibs[mibname] = exc

                if mibname not in processed:
                    processed[mibname] = statusMissing

        debug.logger & debug.flagCompiler and debug.logger(
            'MIBs analyzed %s, MIBs failed %s' % (len(parsedMibs), len(failedMibs)))

        #
        # See what MIBs need generating
        #

        for mibname in tuple(parsedMibs):
            fileInfo, mibInfo, mibTree = parsedMibs[mibname]

            debug.logger & debug.flagCompiler and debug.logger('checking if %s requires updating' % mibname)

            for searcher in self._searchers:
                try:
                    searcher.fileExists(mibname, fileInfo.mtime, rebuild=options.get('rebuild'))

                except error.PySmiFileNotFoundError:
                    debug.logger & debug.flagCompiler and debug.logger(
                        'no compiled MIB %s available through %s' % (mibname, searcher))
                    continue

                except error.PySmiFileNotModifiedError:
                    debug.logger & debug.flagCompiler and debug.logger(
                        'will be using existing compiled MIB %s found by %s' % (mibname, searcher))
                    del parsedMibs[mibname]
                    processed[mibname] = statusUntouched
                    break

                except error.PySmiError:
                    exc_class, exc, tb = sys.exc_info()
                    exc.searcher = searcher
                    exc.mibname = mibname
                    exc.msg += ' at MIB %s' % mibname
                    debug.logger & debug.flagCompiler and debug.logger('error from %s: %s' % (searcher, exc))
                    continue

            else:
                debug.logger & debug.flagCompiler and debug.logger(
                    'no suitable compiled MIB %s found anywhere' % mibname)

                if options.get('noDeps') and mibname not in canonicalMibNames:
                    debug.logger & debug.flagCompiler and debug.logger(
                        'excluding imported MIB %s from code generation' % mibname)
                    del parsedMibs[mibname]
                    processed[mibname] = statusUntouched
                    continue

        debug.logger & debug.flagCompiler and debug.logger(
            'MIBs parsed %s, MIBs failed %s' % (len(parsedMibs), len(failedMibs)))

        #
        # Generate code for parsed MIBs
        #

        for mibname in parsedMibs.copy():
            fileInfo, mibInfo, mibTree = parsedMibs[mibname]

            debug.logger & debug.flagCompiler and debug.logger('compiling %s read from %s' % (mibname, fileInfo.path))

            platform_info, user_info = self._get_system_info()

            comments = [
                'ASN.1 source %s' % fileInfo.path,
                'Produced by %s-%s at %s' % (packageName, packageVersion, time.asctime()),
                'On host %s platform %s version %s by user %s' % (platform_info[1], platform_info[0],
                                                                  platform_info[2], user_info[0]),
                'Using Python version %s' % sys.version.split('\n')[0]
            ]

            try:
                mibInfo, mibData = self._codegen.genCode(
                    mibTree,
                    symbolTableMap,
                    comments=comments,
                    dstTemplate=options.get('dstTemplate'),
                    genTexts=options.get('genTexts'),
                    textFilter=options.get('textFilter')
                )

                builtMibs[mibname] = fileInfo, mibInfo, mibData
                del parsedMibs[mibname]

                debug.logger & debug.flagCompiler and debug.logger(
                    '%s read from %s and compiled by %s' % (mibname, fileInfo.path, self._writer))

            except error.PySmiError:
                exc_class, exc, tb = sys.exc_info()
                exc.handler = self._codegen
                exc.mibname = mibname
                exc.msg += ' at MIB %s' % mibname

                debug.logger & debug.flagCompiler and debug.logger('error from %s: %s' % (self._codegen, exc))

                processed[mibname] = statusFailed.setOptions(error=exc)

                failedMibs[mibname] = exc
                del parsedMibs[mibname]

        debug.logger & debug.flagCompiler and debug.logger(
            'MIBs built %s, MIBs failed %s' % (len(parsedMibs), len(failedMibs)))

        #
        # Try to borrow pre-compiled MIBs for failed ones
        #

        for mibname in failedMibs.copy():
            if options.get('noDeps') and mibname not in canonicalMibNames:
                debug.logger & debug.flagCompiler and debug.logger('excluding imported MIB %s from borrowing' % mibname)
                continue

            for borrower in self._borrowers:
                debug.logger & debug.flagCompiler and debug.logger('trying to borrow %s from %s' % (mibname, borrower))
                try:
                    fileInfo, fileData = borrower.getData(
                        mibname,
                        genTexts=options.get('genTexts')
                    )

                    borrowedMibs[mibname] = fileInfo, MibInfo(name=mibname, imported=[]), fileData

                    del failedMibs[mibname]

                    debug.logger & debug.flagCompiler and debug.logger('%s borrowed with %s' % (mibname, borrower))
                    break

                except error.PySmiError:
                    debug.logger & debug.flagCompiler and debug.logger('error from %s: %s' % (borrower, sys.exc_info()[1]))

        debug.logger & debug.flagCompiler and debug.logger(
            'MIBs available for borrowing %s, MIBs failed %s' % (len(borrowedMibs), len(failedMibs)))

        #
        # See what MIBs need borrowing
        #

        for mibname in borrowedMibs.copy():
            debug.logger & debug.flagCompiler and debug.logger('checking if failed MIB %s requires borrowing' % mibname)

            fileInfo, mibInfo, mibData = borrowedMibs[mibname]

            for searcher in self._searchers:
                try:
                    searcher.fileExists(mibname, fileInfo.mtime, rebuild=options.get('rebuild'))

                except error.PySmiFileNotFoundError:
                    debug.logger & debug.flagCompiler and debug.logger(
                        'no compiled MIB %s available through %s' % (mibname, searcher))
                    continue

                except error.PySmiFileNotModifiedError:
                    debug.logger & debug.flagCompiler and debug.logger(
                        'will be using existing compiled MIB %s found by %s' % (mibname, searcher))
                    del borrowedMibs[mibname]
                    processed[mibname] = statusUntouched
                    break

                except error.PySmiError:
                    exc_class, exc, tb = sys.exc_info()
                    exc.searcher = searcher
                    exc.mibname = mibname
                    exc.msg += ' at MIB %s' % mibname

                    debug.logger & debug.flagCompiler and debug.logger('error from %s: %s' % (searcher, exc))

                    continue
            else:
                debug.logger & debug.flagCompiler and debug.logger(
                    'no suitable compiled MIB %s found anywhere' % mibname)

                if options.get('noDeps') and mibname not in canonicalMibNames:
                    debug.logger & debug.flagCompiler and debug.logger(
                        'excluding imported MIB %s from borrowing' % mibname)
                    processed[mibname] = statusUntouched

                else:
                    debug.logger & debug.flagCompiler and debug.logger('will borrow MIB %s' % mibname)
                    builtMibs[mibname] = borrowedMibs[mibname]

                    processed[mibname] = statusBorrowed.setOptions(
                        path=fileInfo.path, file=fileInfo.file,
                        alias=fileInfo.name
                    )

                del borrowedMibs[mibname]

        debug.logger & debug.flagCompiler and debug.logger(
            'MIBs built %s, MIBs failed %s' % (len(builtMibs), len(failedMibs)))

        #
        # We could attempt to ignore missing/failed MIBs
        #

        if failedMibs and not options.get('ignoreErrors'):
            debug.logger & debug.flagCompiler and debug.logger('failing with problem MIBs %s' % ', '.join(failedMibs))

            for mibname in builtMibs:
                processed[mibname] = statusUnprocessed

            return processed

        debug.logger & debug.flagCompiler and debug.logger(
            'proceeding with built MIBs %s, failed MIBs %s' % (', '.join(builtMibs), ', '.join(failedMibs)))

        #
        # Store compiled MIBs
        #

        for mibname in builtMibs.copy():
            fileInfo, mibInfo, mibData = builtMibs[mibname]

            try:
                if options.get('writeMibs', True):
                    self._writer.putData(
                        mibname, mibData, dryRun=options.get('dryRun')
                    )

                debug.logger & debug.flagCompiler and debug.logger('%s stored by %s' % (mibname, self._writer))

                del builtMibs[mibname]

                if mibname not in processed:
                    processed[mibname] = statusCompiled.setOptions(
                        path=fileInfo.path,
                        file=fileInfo.file,
                        alias=fileInfo.name,
                        oid=mibInfo.oid,
                        oids=mibInfo.oids,
                        identity=mibInfo.identity,
                        revision=mibInfo.revision,
                        enterprise=mibInfo.enterprise,
                        compliance=mibInfo.compliance,
                    )

            except error.PySmiError:
                exc_class, exc, tb = sys.exc_info()
                exc.handler = self._codegen
                exc.mibname = mibname
                exc.msg += ' at MIB %s' % mibname

                debug.logger & debug.flagCompiler and debug.logger('error %s from %s' % (exc, self._writer))

                processed[mibname] = statusFailed.setOptions(error=exc)
                failedMibs[mibname] = exc
                del builtMibs[mibname]

        debug.logger & debug.flagCompiler and debug.logger(
            'MIBs modified: %s' % ', '.join([x for x in processed if processed[x] in ('compiled', 'borrowed')]))

        return processed

    def buildIndex(self, processedMibs, **options):
        platform_info, user_info = self._get_system_info()

        comments = [
            'Produced by %s-%s at %s' % (packageName, packageVersion, time.asctime()),
            'On host %s platform %s version %s by user %s' % (platform_info[1], platform_info[0],
                                                              platform_info[2], user_info[0]),
            'Using Python version %s' % sys.version.split('\n')[0]
        ]

        try:
            self._writer.putData(
                self.indexFile,
                self._codegen.genIndex(
                    processedMibs,
                    comments=comments,
                    old_index_data=self._writer.getData(self.indexFile)
                ),
                dryRun=options.get('dryRun')
            )
        except error.PySmiError:
            exc_class, exc, tb = sys.exc_info()
            exc.msg += ' at MIB index %s' % self.indexFile

            debug.logger & debug.flagCompiler and debug.logger('error %s when building %s' % (exc, self.indexFile))

            if options.get('ignoreErrors'):
                return

            if hasattr(exc, 'with_traceback'):
                raise exc.with_traceback(tb)
            else:
                raise exc class Printer(object):
    class NullHandler(logging.Handler):
class Debug(object):
    def __init__(self, logger=None, handler=None, formatter=None):
    def __call__(self, msg):
    def __str__(self):
    def getCurrentLogger(self):
        def emit(self, record):
    def __init__(self, *flags, **options):
    def __str__(self):
    def __call__(self, msg):
    def __and__(self, flag):
    def __rand__(self, flag):
    def getCurrentPrinter(self):
    def getCurrentLogger(self):
def setLogger(l): class PySmiError(Exception):
class PySmiLexerError(PySmiError):
class PySmiParserError(PySmiLexerError):
class PySmiSyntaxError(PySmiParserError):
class PySmiSearcherError(PySmiError):
class PySmiFileNotModifiedError(PySmiSearcherError):
class PySmiFileNotFoundError(PySmiSearcherError):
class PySmiReaderError(PySmiError):
class PySmiReaderFileNotModifiedError(PySmiReaderError):
class PySmiReaderFileNotFoundError(PySmiReaderError):
class PySmiCodegenError(PySmiError):
class PySmiSemanticError(PySmiCodegenError):
class PySmiWriterError(PySmiError):
    def __init__(self, *args, **kwargs):
    def __repr__(self):
    def __str__(self):
    def __str__(self):
import os
import sys
import getopt
from pysmi.reader import getReadersFromUrls
from pysmi.searcher import AnyFileSearcher, PyFileSearcher, PyPackageSearcher, StubSearcher
from pysmi.borrower import AnyFileBorrower, PyFileBorrower
from pysmi.writer import PyFileWriter, FileWriter, CallbackWriter
from pysmi.parser import SmiV1CompatParser
from pysmi.codegen import PySnmpCodeGen, JsonCodeGen, NullCodeGen
from pysmi.compiler import MibCompiler
from pysmi import debug
from pysmi import error
        from pysmi import __version__
    if opt[0] == '--destination-template':
        dstTemplate = opt[1]

    if opt[0] == '--destination-directory':
        dstDirectory = opt[1]

    if opt[0] == '--cache-directory':
        cacheDirectory = opt[1]

    if opt[0] == '--no-dependencies':
        nodepsFlag = True

    if opt[0] == '--no-python-compile':
        pyCompileFlag = False

    if opt[0] == '--python-optimization-level':
        try:
            pyOptimizationLevel = int(opt[1])

        except ValueError:
            sys.stderr.write('ERROR: known Python optimization levels: -1, 0, 1, 2\r\n%s\r\n' % helpMessage)
            sys.exit(EX_USAGE)

    if opt[0] == '--ignore-errors':
        ignoreErrorsFlag = True

    if opt[0] == '--build-index':
        buildIndexFlag = True

    if opt[0] == '--rebuild':
        rebuildFlag = True

    if opt[0] == '--dry-run':
        dryrunFlag = True

    if opt[0] == '--no-mib-writes':
        writeMibsFlag = False

    if opt[0] == '--generate-mib-texts':
        genMibTextsFlag = True

    if opt[0] == '--disable-fuzzy-source':
        doFuzzyMatchingFlag = False

    if opt[0] == '--keep-texts-layout':
        keepTextsLayout = True

if not mibSources:
    mibSources = ['file:///usr/share/snmp/mibs',
                  'http://mibs.snmplabs.com/asn1/@mib@']

if inputMibs:
    mibSources = sorted(
        set([os.path.abspath(os.path.dirname(x))
            for x in inputMibs
            if os.path.sep in x])
    ) + mibSources

    inputMibs = [os.path.basename(os.path.splitext(x)[0]) for x in inputMibs]

if not inputMibs:
    sys.stderr.write('ERROR: MIB modules names not specified\r\n%s\r\n' % helpMessage)
    sys.exit(EX_USAGE)

if not dstFormat:
    dstFormat = 'pysnmp'

if dstFormat == 'pysnmp':
    if not mibSearchers:
        mibSearchers = PySnmpCodeGen.defaultMibPackages

    if not mibStubs:
        mibStubs = [x for x in PySnmpCodeGen.baseMibs if x not in PySnmpCodeGen.fakeMibs]

    if not mibBorrowers:
        mibBorrowers = [('http://mibs.snmplabs.com/pysnmp/notexts/@mib@', False),
                        ('http://mibs.snmplabs.com/pysnmp/fulltexts/@mib@', True)]

    if not dstDirectory:
        dstDirectory = os.path.expanduser("~")
        if sys.platform[:3] == 'win':
            dstDirectory = os.path.join(dstDirectory, 'PySNMP Configuration', 'mibs')
        else:
            dstDirectory = os.path.join(dstDirectory, '.pysnmp', 'mibs')

    # Compiler infrastructure

    borrowers = [PyFileBorrower(x[1], genTexts=mibBorrowers[x[0]][1])
                 for x in enumerate(getReadersFromUrls(*[m[0] for m in mibBorrowers], **dict(lowcaseMatching=False)))]

    searchers = [PyFileSearcher(dstDirectory)]

    for mibSearcher in mibSearchers:
        searchers.append(PyPackageSearcher(mibSearcher))

    searchers.append(StubSearcher(*mibStubs))

    codeGenerator = PySnmpCodeGen()

    fileWriter = PyFileWriter(dstDirectory).setOptions(pyCompile=pyCompileFlag,
                                                       pyOptimizationLevel=pyOptimizationLevel)

elif dstFormat == 'json':
    if not mibStubs:
        mibStubs = JsonCodeGen.baseMibs

    if not mibBorrowers:
        mibBorrowers = [('http://mibs.snmplabs.com/json/notexts/@mib@', False),
                        ('http://mibs.snmplabs.com/json/fulltexts/@mib@', True)]

    if not dstDirectory:
        dstDirectory = os.path.join('.')

    # Compiler infrastructure

    borrowers = [AnyFileBorrower(x[1], genTexts=mibBorrowers[x[0]][1]).setOptions(exts=['.json'])
                 for x in enumerate(getReadersFromUrls(*[m[0] for m in mibBorrowers], **dict(lowcaseMatching=False)))]

    searchers = [AnyFileSearcher(dstDirectory).setOptions(exts=['.json']), StubSearcher(*mibStubs)]


    codeGenerator = JsonCodeGen()