#!/usr/bin/python -ttOu
# -*- coding: utf-8 -*-
#############################################################################
# File          : rpmlint
# Package       : rpmlint
# Author        : Frederic Lepied
# Created on    : Mon Sep 27 19:20:18 1999
# Purpose       : main entry point: process options, load the checks and run
#                 the checks.
#############################################################################

import getopt
import glob
import imp
import locale
import os
import re
import stat
import sys
import tempfile

# 1 instead of 0 here because we want the script dir to be looked up first,
# e.g. for in-place from tarball or SCM checkout
sys.path.insert(1, '/usr/share/rpmlint')

# Do not import anything that initializes its global variables from
# Config at load time here (or anything that imports such a thing),
# that results in those variables initialized before config files are
# loaded which is too early - settings from config files won't take
# place for those variables.

from Filter import badnessScore, badnessThreshold, printAllReasons, \
    printDescriptions, printInfo, printed_messages, setRawOut
import AbstractCheck
import Config
import Pkg


_default_user_conf = '%s/rpmlint' % \
    (os.environ.get('XDG_CONFIG_HOME') or '~/.config')


# Print usage information
def usage(name):
    print ('''usage: %s [<options>] <rpm files|installed packages|specfiles|dirs>
  options:
\t[-i|--info]
\t[-I|--explain <messageid>]
\t[-c|--check <check>]
\t[-a|--all]
\t[-C|--checkdir <checkdir>]
\t[-h|--help]
\t[-v|--verbose]
\t[-E|--extractdir <dir>]
\t[-V|--version]
\t[-n|--noexception]
\t[   --rawout <file>]
\t[-f|--file <user config file to use instead of %s]
\t[-o|--option <key value>]'''
           % (name, _default_user_conf))


# Print version information
def printVersion():
    print ('rpmlint version %s Copyright (C) 1999-2007 Frederic Lepied, Mandriva' % Config.__version__)


def loadCheck(name):
    '''Load a (check) module by its name, unless it is already loaded.'''
    # Avoid loading more than once (initialization costs)
    loaded = sys.modules.get(name)
    if loaded:
        return loaded
    (fobj, pathname, description) = imp.find_module(name)
    try:
        imp.load_module(name, fobj, pathname, description)
    finally:
        fobj.close()


#############################################################################
# main program
#############################################################################
def main():

    locale.setlocale(locale.LC_COLLATE, '')

    # Add check dirs to the front of load path
    sys.path[0:0] = Config.checkDirs()

    # Load all checks
    for c in Config.allChecks():
        loadCheck(c)

    packages_checked = 0
    specfiles_checked = 0

    try:
        # Loop over all file names given in arguments
        dirs = []
        for arg in args:
            pkgs = []
            isfile = False
            try:
                if arg == "-":
                    arg = "(standard input)"
                    # Short-circuit stdin spec file check
                    stdin = sys.stdin.readlines()
                    if not stdin:
                        continue
                    pkg = Pkg.FakePkg(arg)
                    runSpecChecks(pkg, arg)
                    specfiles_checked += 1
                    continue

                try:
                    st = os.stat(arg)
                    isfile = True
                    if stat.S_ISREG(st[stat.ST_MODE]):
                        if arg.endswith(".spec"):
                            # Short-circuit spec file checks
                            pkg = Pkg.FakePkg(arg)
                            runSpecChecks(pkg, arg)
                            specfiles_checked += 1
                        elif "/" in arg or arg.endswith(".rpm") or \
                                arg.endswith(".spm"):
                            pkgs.append(Pkg.Pkg(arg, extract_dir))
                        else:
                            raise OSError

                    elif stat.S_ISDIR(st[stat.ST_MODE]):
                        dirs.append(arg)
                        continue
                    else:
                        raise OSError
                except OSError:
                    ipkgs = Pkg.getInstalledPkgs(arg)
                    if not ipkgs:
                        Pkg.warn(
                            '(none): E: no installed packages by name %s' % arg)
                    else:
                        ipkgs.sort(key=lambda x: locale.strxfrm(
                            x.header.sprintf("%{NAME}.%{ARCH}")))
                        pkgs.extend(ipkgs)
            except KeyboardInterrupt:
                if isfile:
                    arg = os.path.abspath(arg)
                Pkg.warn(
                    '(none): E: interrupted, exiting while reading %s' % arg)
                sys.exit(2)
            except Exception:
                if isfile:
                    arg = os.path.abspath(arg)
                Pkg.warn('(none): E: error while reading %s: %s' %
                         (arg, sys.exc_info()[1]))
                pkgs = []
                continue

            for pkg in pkgs:
                runChecks(pkg)
                packages_checked += 1

        for dname in dirs:
            try:
                for path, dirs, files in os.walk(dname):
                    for fname in files:
                        fname = os.path.abspath(os.path.join(path, fname))
                        try:
                            if fname.endswith('.rpm') or \
                               fname.endswith('.spm'):
                                pkg = Pkg.Pkg(fname, extract_dir)
                                runChecks(pkg)
                                packages_checked += 1

                            elif fname.endswith('.spec'):
                                pkg = Pkg.FakePkg(fname)
                                runSpecChecks(pkg, fname)
                                specfiles_checked += 1

                        except KeyboardInterrupt:
                            Pkg.warn('(none): E: interrupted, exiting while ' +
                                     'reading %s' % fname)
                            sys.exit(2)
                        except Exception:
                            Pkg.warn(
                                '(none): E: while reading %s: %s' %
                                (fname, sys.exc_info()[1]))
                            continue
            except Exception:
                Pkg.warn(
                    '(none): E: error while reading dir %s: %s' %
                    (dname, sys.exc_info()[1]))
                continue

        if printAllReasons():
            Pkg.warn('(none): E: badness %d exceeds threshold %d, aborting.' %
                     (badnessScore(), badnessThreshold()))
            sys.exit(66)

    finally:
        print("%d packages and %d specfiles checked; %d errors, %d warnings."
              % (packages_checked, specfiles_checked,
                 printed_messages["E"], printed_messages["W"]))

    if printed_messages["E"] > 0:
        sys.exit(64)
    sys.exit(0)


def runChecks(pkg):

    try:
        if verbose:
            printInfo(pkg, 'checking')

        for name in Config.allChecks():
            check = AbstractCheck.AbstractCheck.known_checks.get(name)
            if check:
                check.verbose = verbose
                check.check(pkg)
            else:
                Pkg.warn('(none): W: unknown check %s, skipping' % name)
    finally:
        pkg.cleanup()


def runSpecChecks(pkg, fname):

    try:
        if verbose:
            printInfo(pkg, 'checking')

        for name in Config.allChecks():
            check = AbstractCheck.AbstractCheck.known_checks.get(name)
            if check:
                check.verbose = verbose
                check.check_spec(pkg, fname)
            else:
                Pkg.warn('(none): W: unknown check %s, skipping' % name)
    finally:
        pkg.cleanup()

#############################################################################
#
#############################################################################

argv0 = os.path.basename(sys.argv[0])

# parse options
try:
    (opt, args) = getopt.getopt(sys.argv[1:],
                              'iI:c:C:hVvanE:f:o:',
                              ['info',
                               'explain=',
                               'check=',
                               'checkdir=',
                               'help',
                               'version',
                               'verbose',
                               'all',
                               'noexception',
                               'extractdir=',
                               'file=',
                               'option=',
                               'rawout=',
                               ])
except getopt.GetoptError:
    Pkg.warn("%s: %s" % (argv0, sys.exc_info()[1]))
    usage(argv0)
    sys.exit(1)

# process options
checkdir = '/usr/share/rpmlint'
checks = []
verbose = False
extract_dir = None
conf_file = _default_user_conf
if not os.path.exists(os.path.expanduser(conf_file)):
    # deprecated backwards compatibility with < 0.88
    conf_file = '~/.rpmlintrc'
info_error = set()

# load global config files
configs = glob.glob('/etc/rpmlint/*config')
configs.sort()

# Was rpmlint invoked as a prefixed variant?
m = re.match(r"(?P<prefix>[\w-]+)-rpmlint(\.py)?", argv0)
if m:
    # Okay, we're a prefixed variant. Look for the variant config.
    # If we find it, use it. If not, fallback to the default.
    prefix = m.group('prefix')
    if os.path.isfile('/usr/share/rpmlint/config.%s' % prefix):
        configs.insert(0, '/usr/share/rpmlint/config.%s' % prefix)
    else:
        configs.insert(0, '/usr/share/rpmlint/config')
else:
    configs.insert(0, '/usr/share/rpmlint/config')

for f in configs:
    try:
        exec(compile(open(f).read(), f, 'exec'))
    except IOError:
        pass
    except Exception:
        Pkg.warn('(none): W: error loading %s, skipping: %s' %
                 (f, sys.exc_info()[1]))
# pychecker fix
del f

config_overrides = {}

# process command line options
for o in opt:
    if o[0] in ('-c', '--check'):
        checks.append(o[1])
    elif o[0] in ('-i', '--info'):
        Config.info = True
    elif o[0] in ('-I', '--explain'):
        # split by comma for deprecated backwards compatibility with < 1.2
        info_error.update(o[1].split(','))
    elif o[0] in ('-h', '--help'):
        usage(argv0)
        sys.exit(0)
    elif o[0] in ('-C', '--checkdir'):
        Config.addCheckDir(o[1])
    elif o[0] in ('-v', '--verbose'):
        verbose = True
    elif o[0] in ('-V', '--version'):
        printVersion()
        sys.exit(0)
    elif o[0] in ('-E', '--extractdir'):
        extract_dir = o[1]
        Config.setOption('ExtractDir', extract_dir)
    elif o[0] in ('-n', '--noexception'):
        Config.no_exception = True
    elif o[0] in ('-a', '--all'):
        if '*' not in args:
            args.append('*')
    elif o[0] in ('-f', '--file'):
        conf_file = o[1]
    elif o[0] in ('-o', '--option'):
        kv = o[1].split(None, 1)
        if len(kv) == 1:
            config_overrides[kv[0]] = None
        else:
            config_overrides[kv[0]] = eval(kv[1])
    elif o[0] in ('--rawout',):
        setRawOut(o[1])

# load user config file
try:
    expconf = os.path.expanduser(conf_file)
    exec(compile(open(expconf).read(), expconf, 'exec'))
except IOError:
    pass
except Exception:
    Pkg.warn('(none): W: error loading %s, skipping: %s' %
             (conf_file, sys.exc_info()[1]))

# apply config overrides
for key, value in config_overrides.items():
    Config.setOption(key, value)

if not extract_dir:
    extract_dir = Config.getOption('ExtractDir', tempfile.gettempdir())

if info_error:
    Config.info = True
    sys.path[0:0] = Config.checkDirs()
    for c in checks:
        Config.addCheck(c)
    for c in Config.allChecks():
        loadCheck(c)
    for e in sorted(info_error):
        print("%s:" % e)
        printDescriptions(e)
    sys.exit(0)

# if no argument print usage
if not args:
    usage(argv0)
    sys.exit(1)

if __name__ == '__main__':
    if checks:
        Config.resetChecks()
        for check in checks:
            Config.addCheck(check)
    main()

# rpmlint ends here

# Local variables:
# indent-tabs-mode: nil
# py-indent-offset: 4
# End:
# ex: ts=4 sw=4 et
