Add version command and diagnostic logging
[mudpy.git] / mudpy / version.py
diff --git a/mudpy/version.py b/mudpy/version.py
new file mode 100644 (file)
index 0000000..58d7e7f
--- /dev/null
@@ -0,0 +1,87 @@
+"""Version and diagnostic information for the mudpy engine."""
+
+# Copyright (c) 2018 Jeremy Stanley <fungi@yuggoth.org>. Permission
+# to use, copy, modify, and distribute this software is granted under
+# terms provided in the LICENSE file distributed with this software.
+
+import json
+import pkg_resources
+import sys
+
+
+class VersionDetail:
+
+    """Version detail for a Python package."""
+
+    def __init__(self, package):
+        self.project_name = _normalize_project(package.project_name)
+        version = package.version
+        self.version_info = tuple(version.split('.'))
+
+        # Build up a human-friendly version string for display purposes
+        self.text = "%s %s" % (self.project_name, version)
+
+        # Obtain Git commit ID from PBR metadata if present
+        dist = pkg_resources.get_distribution(self.project_name)
+        try:
+            self.git_version = json.loads(
+                dist.get_metadata("pbr.json"))["git_version"]
+            self.text = "%s (%s)" % (self.text, self.git_version)
+        except (IOError, KeyError):
+            self.git_version = None
+
+    def __repr__(self):
+        return self.text
+
+
+class Versions:
+
+    """Tracks info on known Python package versions."""
+
+    def __init__(self, project_name):
+        # Normalize the supplied name
+        project_name = _normalize_project(project_name)
+
+        # Python info for convenience
+        self.python_version = "%s Python %s" % (
+            sys.platform, sys.version.split(" ")[0])
+
+        # List of package names for this package's declared dependencies
+        requirements = []
+        for package in pkg_resources.get_distribution(project_name).requires():
+            requirements.append(_normalize_project(package.project_name))
+
+        # Accumulators for Python package versions
+        self.dependencies = {}
+        self.environment = {}
+
+        # Loop over all installed packages
+        for package in pkg_resources.working_set:
+            version = VersionDetail(package)
+            # Sort packages into the corresponding buckets
+            if version.project_name in requirements:
+                # This is a dependency
+                self.dependencies[version.project_name] = version
+            elif version.project_name == project_name:
+                # This is our main package
+                self.version = version
+            else:
+                # This may be a transitive dep, or merely installed
+                self.environment[version.project_name] = version
+
+        self.dependencies_text = ", ".join(
+            sorted([x.text for x in self.dependencies.values()]))
+        self.environment_text = ", ".join(
+            sorted([x.text for x in self.environment.values()]))
+
+    def __repr__(self):
+        return "Running %s on %s with dependencies %s." % (
+            self.version.text,
+            self.python_version,
+            self.dependencies_text,
+            )
+
+
+def _normalize_project(project_name):
+    """Convenience function to normalize Python project names."""
+    return pkg_resources.safe_name(project_name).lower()