diff --git a/Doc/library/cmdline.rst b/Doc/library/cmdline.rst index 78fe95a014ff7c..59629b693ba00f 100644 --- a/Doc/library/cmdline.rst +++ b/Doc/library/cmdline.rst @@ -24,7 +24,7 @@ The following modules have a command-line interface. * :mod:`!idlelib` * :ref:`inspect <inspect-module-cli>` * :ref:`json <json-commandline>` -* :mod:`mimetypes` +* :ref:`mimetypes <mimetypes-cli>` * :mod:`pdb` * :mod:`pickle` * :ref:`pickletools <pickletools-cli>` diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst index 514e773359a9aa..5af69455032d71 100644 --- a/Doc/library/mimetypes.rst +++ b/Doc/library/mimetypes.rst @@ -191,7 +191,7 @@ An example usage of the module:: .. _mimetypes-objects: -MimeTypes Objects +MimeTypes objects ----------------- The :class:`MimeTypes` class may be useful for applications which may want more @@ -307,3 +307,97 @@ than one MIME-type database; it provides an interface similar to the one of the When *strict* is ``True`` (the default), the mapping will be added to the official MIME types, otherwise to the non-standard ones. + + +.. _mimetypes-cli: + +Command-line usage +------------------ + +The :mod:`!mimetypes` module can be executed as a script from the command line. + +.. code-block:: sh + + python -m mimetypes [-h] [-e] [-l] type [type ...] + +The following options are accepted: + +.. program:: mimetypes + +.. cmdoption:: -h + --help + + Show the help message and exit. + +.. cmdoption:: -e + --extension + + Guess extension instead of type. + +.. cmdoption:: -l + --lenient + + Additionally search for some common, but non-standard types. + +By default the script converts MIME types to file extensions. +However, if ``--extension`` is specified, +it converts file extensions to MIME types. + +For each ``type`` entry, the script writes a line into the standard output +stream. If an unknown type occurs, it writes an error message into the +standard error stream and exits with the return code ``1``. + + +.. mimetypes-cli-example: + +Command-line example +-------------------- + +Here are some examples of typical usage of the :mod:`!mimetypes` command-line +interface: + +.. code-block:: console + + $ # get a MIME type by a file name + $ python -m mimetypes filename.png + type: image/png encoding: None + + $ # get a MIME type by a URL + $ python -m mimetypes https://example.com/filename.txt + type: text/plain encoding: None + + $ # get a complex MIME type + $ python -m mimetypes filename.tar.gz + type: application/x-tar encoding: gzip + + $ # get a MIME type for a rare file extension + $ python -m mimetypes filename.pict + error: unknown extension of filename.pict + + $ # now look in the extended database built into Python + $ python -m mimetypes --lenient filename.pict + type: image/pict encoding: None + + $ # get a file extension by a MIME type + $ python -m mimetypes --extension text/javascript + .js + + $ # get a file extension by a rare MIME type + $ python -m mimetypes --extension text/xul + error: unknown type text/xul + + $ # now look in the extended database again + $ python -m mimetypes --extension --lenient text/xul + .xul + + $ # try to feed an unknown file extension + $ python -m mimetypes filename.sh filename.nc filename.xxx filename.txt + type: application/x-sh encoding: None + type: application/x-netcdf encoding: None + error: unknown extension of filename.xxx + + $ # try to feed an unknown MIME type + $ python -m mimetypes --extension audio/aac audio/opus audio/future audio/x-wav + .aac + .opus + error: unknown type audio/future diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 6539b23380b6e0..d4bf19a4d66ef3 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -641,6 +641,13 @@ json mimetypes --------- +* Document the command-line for :mod:`mimetypes`. + It now exits with ``1`` on failure instead of ``0`` + and ``2`` on incorrect command-line parameters instead of ``1``. + Also, errors are printed to stderr instead of stdout and their text is made + tighter. + (Contributed by Oleg Iarygin and Hugo van Kemenade in :gh:`93096`.) + * Add MS and :rfc:`8081` MIME types for fonts: * Embedded OpenType: ``application/vnd.ms-fontobject`` diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index ebad9a9ed27337..6b94fe3c4df756 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -670,50 +670,38 @@ def _default_mime_types(): def _main(): - import getopt + """Run the mimetypes command-line interface.""" import sys - - USAGE = """\ -Usage: mimetypes.py [options] type - -Options: - --help / -h -- print this message and exit - --lenient / -l -- additionally search of some common, but non-standard - types. - --extension / -e -- guess extension instead of type - -More than one type argument may be given. -""" - - def usage(code, msg=''): - print(USAGE) - if msg: print(msg) - sys.exit(code) - - try: - opts, args = getopt.getopt(sys.argv[1:], 'hle', - ['help', 'lenient', 'extension']) - except getopt.error as msg: - usage(1, msg) - - strict = 1 - extension = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--lenient'): - strict = 0 - elif opt in ('-e', '--extension'): - extension = 1 - for gtype in args: - if extension: - guess = guess_extension(gtype, strict) - if not guess: print("I don't know anything about type", gtype) - else: print(guess) - else: - guess, encoding = guess_type(gtype, strict) - if not guess: print("I don't know anything about type", gtype) - else: print('type:', guess, 'encoding:', encoding) + from argparse import ArgumentParser + + parser = ArgumentParser(description='map filename extensions to MIME types') + parser.add_argument( + '-e', '--extension', + action='store_true', + help='guess extension instead of type' + ) + parser.add_argument( + '-l', '--lenient', + action='store_true', + help='additionally search for common but non-standard types' + ) + parser.add_argument('type', nargs='+', help='a type to search') + args = parser.parse_args() + + if args.extension: + for gtype in args.type: + guess = guess_extension(gtype, not args.lenient) + if guess: + print(guess) + else: + sys.exit(f"error: unknown type {gtype}") + else: + for gtype in args.type: + guess, encoding = guess_type(gtype, not args.lenient) + if guess: + print('type:', guess, 'encoding:', encoding) + else: + sys.exit(f"error: media type unknown for {gtype}") if __name__ == '__main__': diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index bceb5c2fa0eb24..b5d1f50099e16a 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -3,9 +3,11 @@ import os import sys import unittest.mock +from os import linesep from test import support from test.support import os_helper +from test.support.script_helper import run_python_until_end from platform import win32_edition try: @@ -390,50 +392,53 @@ def test__all__(self): class MimetypesCliTestCase(unittest.TestCase): - def mimetypes_cmd(self, *args, **kwargs): - support.patch(self, sys, "argv", [sys.executable, *args]) - with support.captured_stdout() as output: - mimetypes._main() - return output.getvalue().strip() + def mimetypes_cmd(cls, *args, **kwargs): + result, _ = run_python_until_end('-m', 'mimetypes', *args) + return result.rc, result.out.decode(), result.err.decode() def test_help_option(self): - support.patch(self, sys, "argv", [sys.executable, "-h"]) - with support.captured_stdout() as output: - with self.assertRaises(SystemExit) as cm: - mimetypes._main() - - self.assertIn("Usage: mimetypes.py", output.getvalue()) - self.assertEqual(cm.exception.code, 0) + retcode, out, err = self.mimetypes_cmd('-h') + self.assertEqual(retcode, 0) + self.assertStartsWith(out, 'usage: ') + self.assertEqual(err, '') def test_invalid_option(self): - support.patch(self, sys, "argv", [sys.executable, "--invalid"]) - with support.captured_stdout() as output: - with self.assertRaises(SystemExit) as cm: - mimetypes._main() - - self.assertIn("Usage: mimetypes.py", output.getvalue()) - self.assertEqual(cm.exception.code, 1) + retcode, out, err = self.mimetypes_cmd('--invalid') + self.assertEqual(retcode, 2) + self.assertEqual(out, '') + self.assertStartsWith(err, 'usage: ') def test_guess_extension(self): - eq = self.assertEqual - - extension = self.mimetypes_cmd("-l", "-e", "image/jpg") - eq(extension, ".jpg") + retcode, out, err = self.mimetypes_cmd('-l', '-e', 'image/jpg') + self.assertEqual(retcode, 0) + self.assertEqual(out, f'.jpg{linesep}') + self.assertEqual(err, '') - extension = self.mimetypes_cmd("-e", "image/jpg") - eq(extension, "I don't know anything about type image/jpg") + retcode, out, err = self.mimetypes_cmd('-e', 'image/jpg') + self.assertEqual(retcode, 1) + self.assertEqual(out, '') + self.assertEqual(err, f'error: unknown type image/jpg{linesep}') - extension = self.mimetypes_cmd("-e", "image/jpeg") - eq(extension, ".jpg") + retcode, out, err = self.mimetypes_cmd('-e', 'image/jpeg') + self.assertEqual(retcode, 0) + self.assertEqual(out, f'.jpg{linesep}') + self.assertEqual(err, '') def test_guess_type(self): - eq = self.assertEqual - - type_info = self.mimetypes_cmd("-l", "foo.pic") - eq(type_info, "type: image/pict encoding: None") - - type_info = self.mimetypes_cmd("foo.pic") - eq(type_info, "I don't know anything about type foo.pic") + retcode, out, err = self.mimetypes_cmd('-l', 'foo.webp') + self.assertEqual(retcode, 0) + self.assertEqual(out, f'type: image/webp encoding: None{linesep}') + self.assertEqual(err, '') + + @unittest.skipIf( + sys.platform == 'darwin', + 'macOS lists common_types in mime.types thus making them always known' + ) + def test_guess_type_conflicting_with_mimetypes(self): + retcode, out, err = self.mimetypes_cmd('foo.pic') + self.assertEqual(retcode, 1) + self.assertEqual(out, '') + self.assertEqual(err, f'error: media type unknown for foo.pic{linesep}') if __name__ == "__main__": unittest.main() diff --git a/Misc/ACKS b/Misc/ACKS index da528f8b4ca3a7..2663042c06afd6 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -849,6 +849,7 @@ Oleg Höfling Robert Hölzl Stefan Hölzl Catalin Iacob +Oleg Iarygin Mihai Ibanescu Ali Ikinci Aaron Iles diff --git a/Misc/NEWS.d/next/Library/2022-05-28-19-41-02.gh-issue-93096.qjUyRG.rst b/Misc/NEWS.d/next/Library/2022-05-28-19-41-02.gh-issue-93096.qjUyRG.rst new file mode 100644 index 00000000000000..fb9ca441c7e2da --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-05-28-19-41-02.gh-issue-93096.qjUyRG.rst @@ -0,0 +1,5 @@ +Document the command-line for :mod:`mimetypes`. +It now exits with ``1`` on failure instead of ``0`` +and ``2`` on incorrect command-line parameters instead of ``1``. +Also, errors are printed to stderr instead of stdout and their text is made +tighter. Patch by Oleg Iarygin and Hugo van Kemenade.