Show package and other improvements

Change-Id: I449a32a68be7d9c87c874c641b353866030297ae
Related-PROD: PROD-28199
diff --git a/cfg_checker/modules/packages/__init__.py b/cfg_checker/modules/packages/__init__.py
index f88ce51..77c1654 100644
--- a/cfg_checker/modules/packages/__init__.py
+++ b/cfg_checker/modules/packages/__init__.py
@@ -60,6 +60,16 @@
         action="store_true", default=False,
         help="Save pkg descriptions while parsing"
     )
+    pkg_show = pkg_subparsers.add_parser(
+        'show',
+        help="Show package history from the map"
+    )
+    pkg_show.add_argument(
+        'args',
+        nargs='+',
+        help="Package names separated by space"
+    )
+
     return _parser
 
 
@@ -89,6 +99,9 @@
     """
     # Get the list of tags for the url
     r = RepoManager()
+    if args.list_tags:
+        r.action_for_tag(args.url, args.tag, action="list")
+        return
     if args.build_repos:
         # if tag is supplied, use it
         if args.tag:
@@ -96,7 +109,6 @@
         else:
             r.build_repos(args.url)
 
-    # if tag is supplied, use it
     if args.tag:
         # Process only this tag
         r.action_for_tag(
@@ -108,3 +120,13 @@
     else:
         # All of them
         r.parse_repos()
+
+
+def do_show(args):
+    """Shows package (or multiple) history across parsed tags
+    """
+    # Init manager
+    r = RepoManager()
+    # show packages
+    for p in args.args:
+        r.show_package(p)
diff --git a/cfg_checker/modules/packages/repos.py b/cfg_checker/modules/packages/repos.py
index 2a91ed5..e592915 100644
--- a/cfg_checker/modules/packages/repos.py
+++ b/cfg_checker/modules/packages/repos.py
@@ -2,7 +2,7 @@
 import os
 from copy import deepcopy
 
-from cfg_checker.common import logger, logger_cli
+from cfg_checker.common import logger, logger_cli, nested_set
 from cfg_checker.common.const import _pkg_desc_archive
 from cfg_checker.common.const import _repos_index_filename
 from cfg_checker.common.const import _repos_info_archive
@@ -20,6 +20,18 @@
 ext = ".json"
 
 
+def get_tag_label(_tag):
+    # prettify the tag for printing
+    _label = ""
+    if _tag.endswith(".update"):
+        _label += "[updates] " + _tag.rsplit('.', 1)[0]
+    elif _tag.endswith(".hotfix"):
+        _label += " [hotfix] " + _tag.rsplit('.', 1)[0]
+    else:
+        _label += " "*10 + _tag
+    return _label
+
+
 def _n_url(url):
     if url[-1] == '/':
         return url
@@ -236,21 +248,37 @@
 
         return
 
-    def list_tags(self):
+    def list_tags(self, splitted=False):
         _files = TGZFile(self._repofile).list_files()
         # all files in archive with no '.json' part
         _all = set([f.rsplit('.', 1)[0] for f in _files])
-        # files that ends with '.update'
-        _updates = set([f for f in _all if f.find('update') >= 0])
-        # files that ends with '.hotfix'
-        _hotfix = set([f for f in _all if f.find('hotfix') >= 0])
-        # remove updates and hotfix tags from all. The true magic of SETs
-        _all = _all - _updates - _hotfix
-        # cut updates and hotfix endings
-        _updates = [f.rsplit('.', 1)[0] for f in _updates]
-        _hotfix = [f.rsplit('.', 1)[0] for f in _hotfix]
+        if splitted:
+            # files that ends with '.update'
+            _updates = set([f for f in _all if f.find('update') >= 0])
+            # files that ends with '.hotfix'
+            _hotfix = set([f for f in _all if f.find('hotfix') >= 0])
+            # remove updates and hotfix tags from all. The true magic of SETs
+            _all = _all - _updates - _hotfix
+            # cut updates and hotfix endings
+            _updates = [f.rsplit('.', 1)[0] for f in _updates]
+            _hotfix = [f.rsplit('.', 1)[0] for f in _hotfix]
 
-        return _all, _updates, _hotfix
+            return _all, _updates, _hotfix
+        else:
+            # dynamic import
+            import re
+            _all = list(_all)
+            # lexical tags
+            _lex = [s for s in _all if not s[0].isdigit()]
+            _lex.sort()
+            # tags with digits
+            _dig = [s for s in _all if s[0].isdigit()]
+            _dig = sorted(
+                _dig,
+                key=lambda x: tuple(int(i) for i in re.findall('\\d+', x)[:3])
+            )
+
+            return _dig + _lex
 
     def get_repoinfo(self, tag):
         _tgz = TGZFile(self._repofile)
@@ -489,7 +517,6 @@
                             _name = _desc['package']
                             _md5 = _desc['md5sum']
                             _version = _desc['version']
-                            _pkg['md5'] = _md5
                             # update version for a package
                             if self._update_pkg_version(
                                 _name,
@@ -557,6 +584,16 @@
         # and gather all of the repos for 'tag' or all of the tags
         _repos.fetch_repos(url, tag=tag)
 
+    def _build_action(self, url, tags):
+        for t in tags:
+            logger_cli.info(
+                "# Building repo info for '{}/{}'".format(
+                    url,
+                    t
+                )
+            )
+            self.build_repos(url, tag=t)
+
     def action_for_tag(
         self,
         url,
@@ -568,57 +605,124 @@
         """
         if not action:
             logger_cli.info("# No action set, nothing to do")
-        # get all tags
-        major, updates, hotfix = ReposInfo().list_tags()
+        # See if this is a list action
         if action == "list":
+            _all = ReposInfo().list_tags()
+            # Print pretty list and exit
             logger_cli.info("# Tags available at '{}':".format(url))
-            for t in major:
-                logger_cli.info("\t{}".format(t))
-            for t in updates:
-                logger_cli.info("\t{} [updates]".format(t))
-            for t in hotfix:
-                logger_cli.info("\t{} [hotfix]".format(t))
+            for t in _all:
+                logger_cli.info(get_tag_label(t))
+            # exit
             return
+
         # Pupulate action tags
+        major, updates, hotfix = ReposInfo().list_tags(splitted=True)
         _action_tags = []
         if tag in major:
             _action_tags.append(tag)
-        elif tag in updates:
+        if tag in updates:
             _action_tags.append(tag + ".update")
-        elif tag in hotfix:
+        if tag in hotfix:
             _action_tags.append(tag + ".hotfix")
-
+        # Check if any tags collected
         if not _action_tags:
             logger_cli.info(
                 "# Tag of '{}' not found. "
                 "Consider rebuilding repos info.".format(tag)
             )
-        elif action == "build":
+        else:
             logger_cli.info(
-                "-> tags to build {}".format(", ".join(_action_tags))
-            )
-            for t in _action_tags:
-                logger_cli.info(
-                    "# Building repo info for '{}/{}'".format(
-                        url,
-                        tag
-                    )
-                )
-                self.build_repos(url, tag=tag)
-        elif action == "fetch":
-            logger_cli.info(
-                "-> fetching versions for tags {}".format(
+                "-> tags to process: {}".format(
                     ", ".join(_action_tags)
                 )
             )
+        # Execute actions
+        if action == "build":
+            self._build_action(url, _action_tags)
+        elif action == "fetch":
             for t in _action_tags:
                 self.fetch_versions(t, descriptions=descriptions)
 
         logger_cli.info("# Done.")
 
+    def show_package(self, name):
+        # get the package data
+        _p = self.get_package_versions(name)
+        if not _p:
+            logger_cli.warning(
+                "# WARNING: Package '{}' not found".format(name)
+            )
+        else:
+            # print package info using sorted tags from headers
+            # Package: name
+            # [u/h] tag \t <version>
+            #           \t <version>
+            # <10symbols> \t <md5> \t sorted headers with no tag
+            # ...
+            logger_cli.info("\n# Package: {}".format(name))
+            _o = ""
+            # get and sort tags
+            _vs = _p.keys()
+            _vs.sort()
+            for _v in _vs:
+                _o += "\n" + " "*8 + _v + ':\n'
+                # get and sort tags
+                _mds = _p[_v].keys()
+                _mds.sort()
+                for _md5 in _mds:
+                    _o += " "*16 + _md5 + "\n"
+                    # get and sort repo headers
+                    _rr = _p[_v][_md5].keys()
+                    _rr.sort()
+                    for _r in _rr:
+                        _o += " "*24 + _r.replace('_', ' ') + "\n"
+
+            logger_cli.info(_o)
+
+    def get_package_versions(self, name, tagged=False):
+        """Method builds package version structure
+        with repository properties included
+        """
+        # get data
+        if name in self._versions:
+            _vs = self._versions[name]
+        else:
+            return {}
+        # insert repo data, insert props into headers place
+        _package = {}
+        if tagged:
+            for _v, _d1 in _vs.iteritems():
+                # use tag as a next step
+                for _md5, _repos in _d1.iteritems():
+                    for _index in _repos:
+                        # extract props for a repo
+                        _repo_props = self._repo_index[_index]
+                        # get tag
+                        _tag = _repo_props["props"]["tag"]
+                        # cut tag from the header
+                        _cut_head = _repo_props["header"].split("_", 1)[1]
+                        # populate dict
+                        nested_set(
+                            _package,
+                            [_tag, _v, _cut_head, _md5],
+                            _repo_props["props"]
+                        )
+        else:
+            for _v, _d1 in _vs.iteritems():
+                for _md5, _repos in _d1.iteritems():
+                    for _index in _repos:
+                        _repo_props = self._repo_index[_index]
+                        nested_set(
+                            _package,
+                            [_v, _md5, _repo_props["header"]],
+                            _repo_props["props"]
+                        )
+
+        return _package
+
     def parse_repos(self):
         # all tags to check
-        major, updates, hotfix = ReposInfo().list_tags()
+        major, updates, hotfix = ReposInfo().list_tags(splitted=True)
 
         # major tags
         logger_cli.info("# Processing major tags")