Close #18626: add a basic CLI for the inspect module
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index aabf85d..40e0158 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -1006,3 +1006,20 @@
       return an empty dictionary.
 
    .. versionadded:: 3.3
+
+
+Command Line Interface
+----------------------
+
+The :mod:`inspect` module also provides a basic introspection capability
+from the command line.
+
+.. program:: inspect
+
+By default, accepts the name of a module and prints the source of that
+module. A class or function within the module can be printed instead by
+appended a colon and the qualified name of the target object.
+
+.. cmdoption:: --details
+
+   Print information about the specified object rather than the source code
diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst
index 60dd94d..0690e70 100644
--- a/Doc/whatsnew/3.4.rst
+++ b/Doc/whatsnew/3.4.rst
@@ -264,11 +264,15 @@
 inspect
 -------
 
+
+The inspect module now offers a basic command line interface to quickly
+display source code and other information for modules, classes and
+functions.
+
 :func:`~inspect.unwrap` makes it easy to unravel wrapper function chains
 created by :func:`functools.wraps` (and any other API that sets the
 ``__wrapped__`` attribute on a wrapper function).
 
-
 mmap
 ----
 
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 371bb35..5feef8f 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -2109,3 +2109,64 @@
             rendered += ' -> {}'.format(anno)
 
         return rendered
+
+def _main():
+    """ Logic for inspecting an object given at command line """
+    import argparse
+    import importlib
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        'object',
+         help="The object to be analysed. "
+              "It supports the 'module:qualname' syntax")
+    parser.add_argument(
+        '-d', '--details', action='store_true',
+        help='Display info about the module rather than its source code')
+
+    args = parser.parse_args()
+
+    target = args.object
+    mod_name, has_attrs, attrs = target.partition(":")
+    try:
+        obj = module = importlib.import_module(mod_name)
+    except Exception as exc:
+        msg = "Failed to import {} ({}: {})".format(mod_name,
+                                                    type(exc).__name__,
+                                                    exc)
+        print(msg, file=sys.stderr)
+        exit(2)
+
+    if has_attrs:
+        parts = attrs.split(".")
+        obj = module
+        for part in parts:
+            obj = getattr(obj, part)
+
+    if module.__name__ in sys.builtin_module_names:
+        print("Can't get info for builtin modules.", file=sys.stderr)
+        exit(1)
+
+    if args.details:
+        print('Target: {}'.format(target))
+        print('Origin: {}'.format(getsourcefile(module)))
+        print('Cached: {}'.format(module.__cached__))
+        if obj is module:
+            print('Loader: {}'.format(repr(module.__loader__)))
+            if hasattr(module, '__path__'):
+                print('Submodule search path: {}'.format(module.__path__))
+        else:
+            try:
+                __, lineno = findsource(obj)
+            except Exception:
+                pass
+            else:
+                print('Line: {}'.format(lineno))
+
+        print('\n')
+    else:
+        print(getsource(obj))
+
+
+if __name__ == "__main__":
+    _main()
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index be55fe4..bcb7d8a 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -9,10 +9,11 @@
 import os
 import shutil
 import functools
+import importlib
 from os.path import normcase
 
 from test.support import run_unittest, TESTFN, DirsOnSysPath
-
+from test.script_helper import assert_python_ok, assert_python_failure
 from test import inspect_fodder as mod
 from test import inspect_fodder2 as mod2
 
@@ -2372,6 +2373,47 @@
             __wrapped__ = func
         self.assertIsNone(inspect.unwrap(C()))
 
+class TestMain(unittest.TestCase):
+    def test_only_source(self):
+        module = importlib.import_module('unittest')
+        rc, out, err = assert_python_ok('-m', 'inspect',
+                                        'unittest')
+        lines = out.decode().splitlines()
+        # ignore the final newline
+        self.assertEqual(lines[:-1], inspect.getsource(module).splitlines())
+        self.assertEqual(err, b'')
+
+    def test_qualname_source(self):
+        module = importlib.import_module('concurrent.futures')
+        member = getattr(module, 'ThreadPoolExecutor')
+        rc, out, err = assert_python_ok('-m', 'inspect',
+                                     'concurrent.futures:ThreadPoolExecutor')
+        lines = out.decode().splitlines()
+        # ignore the final newline
+        self.assertEqual(lines[:-1],
+                         inspect.getsource(member).splitlines())
+        self.assertEqual(err, b'')
+
+    def test_builtins(self):
+        module = importlib.import_module('unittest')
+        _, out, err = assert_python_failure('-m', 'inspect',
+                                            'sys')
+        lines = err.decode().splitlines()
+        self.assertEqual(lines, ["Can't get info for builtin modules."])
+
+    def test_details(self):
+        module = importlib.import_module('unittest')
+        rc, out, err = assert_python_ok('-m', 'inspect',
+                                        'unittest', '--details')
+        output = out.decode()
+        # Just a quick sanity check on the output
+        self.assertIn(module.__name__, output)
+        self.assertIn(module.__file__, output)
+        self.assertIn(module.__cached__, output)
+        self.assertEqual(err, b'')
+
+
+
 
 def test_main():
     run_unittest(
@@ -2380,7 +2422,7 @@
         TestGetcallargsFunctions, TestGetcallargsMethods,
         TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
         TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
-        TestBoundArguments, TestGetClosureVars, TestUnwrap
+        TestBoundArguments, TestGetClosureVars, TestUnwrap, TestMain
     )
 
 if __name__ == "__main__":
diff --git a/Misc/NEWS b/Misc/NEWS
index 2fd27a4..456307a 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@
 Library
 -------
 
+- Issue #18626: the inspect module now offers a basic command line
+  introspection interface (Initial patch by Claudiu Popa)
+
 - Issue #3015: Fixed tkinter with wantobject=False.  Any Tcl command call
   returned empty string.