Current File : //bin/pkgdiff
#!/usr/bin/python2.7
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

#
# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
#

import getopt
import gettext
import locale
import sys
import traceback

import pkg.actions
import pkg.variant as variant
import pkg.client.api_errors as apx
import pkg.manifest as manifest
import pkg.misc as misc
from pkg.misc import PipeError
from collections import defaultdict

def usage(errmsg="", exitcode=2):
        """Emit a usage message and optionally prefix it with a more specific
        error message.  Causes program to exit."""

        if errmsg:
                print >> sys.stderr, "pkgdiff: %s" % errmsg

        print _("""\
Usage:
        pkgdiff [-i attribute]... [-o attribute]
            [-t action_name[,action_name]...]...
            [-v name=value]... (file1 | -) (file2 | -)""")
        sys.exit(exitcode)

def error(text, exitcode=3):
        """Emit an error message prefixed by the command name """

        print >> sys.stderr, "pkgdiff: %s" % text

        if exitcode != None:
                sys.exit(exitcode)

def main_func():
        gettext.install("pkg", "/usr/share/locale",
            codeset=locale.getpreferredencoding())

        ignoreattrs = []
        onlyattrs = []
        onlytypes = []
        varattrs = defaultdict(set)

        try:
                opts, pargs = getopt.getopt(sys.argv[1:], "i:o:t:v:?", ["help"])
                for opt, arg in opts:
                        if opt == "-i":
                                ignoreattrs.append(arg)
                        elif opt == "-o":
                                onlyattrs.append(arg)
                        elif opt == "-t":
                                onlytypes.extend(arg.split(","))
                        elif opt == "-v":
                                args = arg.split("=")
                                if len(args) != 2:
                                        usage(_("variant option incorrect %s") % arg)
                                if not args[0].startswith("variant."):
                                        args[0] = "variant." + args[0]
                                varattrs[args[0]].add(args[1])
                        elif opt in ("--help", "-?"):
                                usage(exitcode=0)

        except getopt.GetoptError, e:
                usage(_("illegal global option -- %s") % e.opt)

        if len(pargs) != 2:
                usage(_("two manifest arguments are required"))

        if (pargs[0] == "-" and pargs[1] == "-"):
                usage(_("only one manifest argument can be stdin"))

        if ignoreattrs and onlyattrs:
                usage(_("-i and -o options may not be used at the same time."))

        for v in varattrs:
                if len(varattrs[v]) > 1:
                        usage(_("For any variant, only one value may be specified."))
                varattrs[v] = varattrs[v].pop()

        ignoreattrs = set(ignoreattrs)
        onlyattrs = set(onlyattrs)
        onlytypes = set(onlytypes)

        utypes = set(
            t
            for t in onlytypes
            if t == "generic" or t not in pkg.actions.types
        )

        if utypes:
                usage(_("unknown action types: %s" %
                    apx.list_to_lang(list(utypes))))

        manifest1 = manifest.Manifest()
        manifest2 = manifest.Manifest()
        try:
                # This assumes that both pargs are not '-'.
                for p, m in zip(pargs, (manifest1, manifest2)):
                        if p == "-":
                                m.set_content(content=sys.stdin.read())
                        else:
                                m.set_content(pathname=p)
        except (pkg.actions.ActionError, apx.InvalidPackageErrors), e:
                error(_("Action error in file %(p)s: %(e)s") % locals())
        except (EnvironmentError, apx.ApiException), e:
                error(e)

        #
        # manifest filtering
        #

        # filter action type
        if onlytypes:
                for m in (manifest1, manifest2):
                        # Must pass complete list of actions to set_content, not
                        # a generator, to avoid clobbering manifest contents.
                        m.set_content(content=list(m.gen_actions_by_types(
                            onlytypes)))

        # filter variant
        v1 = manifest1.get_all_variants()
        v2 = manifest2.get_all_variants()
        for vname in varattrs:
                for path, v, m in zip(pargs, (v1, v2), (manifest1, manifest2)):
                        if vname not in v:
                                continue
                        filt = varattrs[vname]
                        if filt not in v[vname]:
                                usage(_("Manifest %(path)s doesn't support variant %(vname)s=%(filt)s" %
                                    locals()))
                        # remove the variant tag
                        def rip(a):
                                a.attrs.pop(vname, None)
                                return a
                        m.set_content([
                            rip(a)
                            for a in m.gen_actions(excludes=[
                            variant.Variants({vname: filt}).allow_action])
                        ])
                        m[vname] = filt

        if varattrs:
                # need to rebuild these if we're filtering variants
                v1 = manifest1.get_all_variants()
                v2 = manifest2.get_all_variants()

        # we need to be a little clever about variants, since
        # we can have multiple actions w/ the same key attributes
        # in each manifest in that case.  First, make sure any variants
        # of the same name have the same values defined.
        for k in set(v1.keys()) & set(v2.keys()):
                if v1[k] != v2[k]:
                        error(_("Manifests support different variants "
                            "%(v1)s %(v2)s") % {"v1": v1, "v2": v2})

        # Now, get a list of all possible variant values, including None
        # across all variants and both manifests
        v_values = dict()

        for v in v1:
                v1[v].add(None)
                for a in v1[v]:
                        v_values.setdefault(v, set()).add((v, a))

        for v in v2:
                v2[v].add(None)
                for a in v2[v]:
                        v_values.setdefault(v, set()).add((v, a))

        diffs = []

        for tup in product(*v_values.values()):
                # build excludes closure to examine only actions exactly
                # matching current variant values... this is needed to
                # avoid confusing manifest difference code w/ multiple
                # actions w/ same key attribute values or getting dups
                # in output
                def allow(a, publisher=None):
                        for k, v in tup:
                                if v is not None:
                                        if k not in a.attrs or a.attrs[k] != v:
                                                return False
                                elif k in a.attrs:
                                        return False
                        return True

                a, c, r = manifest2.difference(manifest1, [allow], [allow])
                diffs += a
                diffs += c
                diffs += r

        # License action still causes spurious diffs... check again for now.
        real_diffs = [
            (a,b)
            for a, b in diffs
            if a is None or b is None or a.different(b)
        ]

        if not real_diffs:
                return 0

        # define some ordering functions so that output is easily readable
        # First, a human version of action comparison that works across
        # variants and action changes...
        def compare(a, b):
                if hasattr(a, "key_attr") and hasattr(b, "key_attr") and \
                    a.key_attr == b.key_attr:
                        res = cmp(a.attrs[a.key_attr], b.attrs[b.key_attr])
                        if res:
                                return res
                        # sort by variant
                        res = cmp(sorted(list(a.get_variant_template())), sorted(list(b.get_variant_template())))
                        if res:
                                return res
                else:
                        res = cmp(a.ordinality, b.ordinality)
                        if res:
                                return res
                return cmp(str(a), str(b))

        # and something to pull the relevant action out of the old value, new
        # value tuples
        def tuple_key(a):
                if not a[0]:
                        return a[1]
                return a[0]

        # sort and....
        diffs = sorted(diffs, key=tuple_key, cmp=compare)

        # handle list attributes
        def attrval(attrs, k, elide_iter=tuple()):
                def q(s):
                        if " " in s or s == "":
                                return '"%s"' % s
                        else:
                                return s

                v = attrs[k]
                if isinstance(v, list) or isinstance(v, set):
                        out = " ".join(["%s=%s" %
                            (k, q(lmt)) for lmt in sorted(v) if lmt not in elide_iter])
                elif " " in v or v == "":
                        out = k + "=\"" + v + "\""
                else:
                        out = k + "=" + v
                return out

        # figure out when to print diffs
        def conditional_print(s, a):
                if onlyattrs:
                        if not set(a.attrs.keys()) & onlyattrs:
                                return False
                elif ignoreattrs:
                        if not set(a.attrs.keys()) - ignoreattrs:
                                return False
                print "%s %s" % (s, a)
                return True

        different = False

        for old, new in diffs:
                if not new:
                        different |= conditional_print("-", old)
                elif not old:
                        different |= conditional_print("+", new)
                else:
                        s = []

                        if not onlyattrs:
                                if hasattr(old, "hash") and "hash" not in ignoreattrs:
                                        if old.hash != new.hash:
                                                s.append("  - %s" % new.hash)
                                                s.append("  + %s" % old.hash)
                                attrdiffs = set(new.differences(old)) - ignoreattrs
                                attrsames = sorted(list(set(old.attrs.keys() + new.attrs.keys()) -
                                    set(new.differences(old))))
                        else:
                                if hasattr(old, "hash") and "hash"  in onlyattrs:
                                        if old.hash != new.hash:
                                                s.append("  - %s" % new.hash)
                                                s.append("  + %s" % old.hash)
                                attrdiffs = set(new.differences(old)) & onlyattrs
                                attrsames = sorted(list(set(old.attrs.keys() + new.attrs.keys()) -
                                    set(new.differences(old))))

                        for a in sorted(attrdiffs):
                                if a in old.attrs and a in new.attrs and \
                                    isinstance(old.attrs[a], list) and \
                                    isinstance(new.attrs[a], list):
                                        elide_set = set(old.attrs[a]) & set(new.attrs[a])
                                else:
                                        elide_set = set()
                                if a in old.attrs:
                                        diff_str = attrval(old.attrs, a, elide_iter=elide_set)
                                        if diff_str:
                                                s.append("  - %s" % diff_str)
                                if a in new.attrs:
                                        diff_str = attrval(new.attrs, a, elide_iter=elide_set)
                                        if diff_str:
                                                s.append("  + %s" % diff_str)
                        # print out part of action that is the same
                        if s:
                                different = True
                                print "%s %s %s" % (old.name,
                                    attrval(old.attrs, old.key_attr),
                                    " ".join(("%s" % attrval(old.attrs,v)
                                    for v in attrsames if v != old.key_attr)))
                                for l in s:
                                        print l

        return int(different)

def product(*args, **kwds):
        # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
        # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
        # from python 2.7 itertools
        pools = map(tuple, args) * kwds.get('repeat', 1)
        result = [[]]
        for pool in pools:
                result = [x+[y] for x in result for y in pool]
        for prod in result:
                yield tuple(prod)

if __name__ == "__main__":
        try:
                exit_code = main_func()
        except (PipeError, KeyboardInterrupt):
                exit_code = 1
        except SystemExit, __e:
                exit_code = __e
        except Exception, __e:
                traceback.print_exc()
                error(misc.get_traceback_message(), exitcode=None)
                exit_code = 99

        sys.exit(exit_code)