gh-93096: Make mimetypes CLI tool public (#93097)

Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
This commit is contained in:
Oleg Iarygin 2025-03-13 15:19:22 +04:00 committed by GitHub
parent 119bcfad9c
commit 328f8b8856
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 176 additions and 76 deletions

View File

@ -24,7 +24,7 @@ The following modules have a command-line interface.
* :mod:`!idlelib` * :mod:`!idlelib`
* :ref:`inspect <inspect-module-cli>` * :ref:`inspect <inspect-module-cli>`
* :ref:`json <json-commandline>` * :ref:`json <json-commandline>`
* :mod:`mimetypes` * :ref:`mimetypes <mimetypes-cli>`
* :mod:`pdb` * :mod:`pdb`
* :mod:`pickle` * :mod:`pickle`
* :ref:`pickletools <pickletools-cli>` * :ref:`pickletools <pickletools-cli>`

View File

@ -191,7 +191,7 @@ An example usage of the module::
.. _mimetypes-objects: .. _mimetypes-objects:
MimeTypes Objects MimeTypes objects
----------------- -----------------
The :class:`MimeTypes` class may be useful for applications which may want more 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 When *strict* is ``True`` (the default), the mapping will be added to the
official MIME types, otherwise to the non-standard ones. 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

View File

@ -652,6 +652,13 @@ json
mimetypes 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: * Add MS and :rfc:`8081` MIME types for fonts:
* Embedded OpenType: ``application/vnd.ms-fontobject`` * Embedded OpenType: ``application/vnd.ms-fontobject``

View File

@ -670,50 +670,38 @@ _default_mime_types()
def _main(): def _main():
import getopt """Run the mimetypes command-line interface."""
import sys import sys
from argparse import ArgumentParser
USAGE = """\ parser = ArgumentParser(description='map filename extensions to MIME types')
Usage: mimetypes.py [options] type 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()
Options: if args.extension:
--help / -h -- print this message and exit for gtype in args.type:
--lenient / -l -- additionally search of some common, but non-standard guess = guess_extension(gtype, not args.lenient)
types. if guess:
--extension / -e -- guess extension instead of type print(guess)
else:
More than one type argument may be given. sys.exit(f"error: unknown type {gtype}")
""" else:
for gtype in args.type:
def usage(code, msg=''): guess, encoding = guess_type(gtype, not args.lenient)
print(USAGE) if guess:
if msg: print(msg) print('type:', guess, 'encoding:', encoding)
sys.exit(code) else:
sys.exit(f"error: media type unknown for {gtype}")
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)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -3,9 +3,11 @@ import mimetypes
import os import os
import sys import sys
import unittest.mock import unittest.mock
from os import linesep
from test import support from test import support
from test.support import os_helper from test.support import os_helper
from test.support.script_helper import run_python_until_end
from platform import win32_edition from platform import win32_edition
try: try:
@ -390,50 +392,53 @@ class MiscTestCase(unittest.TestCase):
class MimetypesCliTestCase(unittest.TestCase): class MimetypesCliTestCase(unittest.TestCase):
def mimetypes_cmd(self, *args, **kwargs): def mimetypes_cmd(cls, *args, **kwargs):
support.patch(self, sys, "argv", [sys.executable, *args]) result, _ = run_python_until_end('-m', 'mimetypes', *args)
with support.captured_stdout() as output: return result.rc, result.out.decode(), result.err.decode()
mimetypes._main()
return output.getvalue().strip()
def test_help_option(self): def test_help_option(self):
support.patch(self, sys, "argv", [sys.executable, "-h"]) retcode, out, err = self.mimetypes_cmd('-h')
with support.captured_stdout() as output: self.assertEqual(retcode, 0)
with self.assertRaises(SystemExit) as cm: self.assertStartsWith(out, 'usage: ')
mimetypes._main() self.assertEqual(err, '')
self.assertIn("Usage: mimetypes.py", output.getvalue())
self.assertEqual(cm.exception.code, 0)
def test_invalid_option(self): def test_invalid_option(self):
support.patch(self, sys, "argv", [sys.executable, "--invalid"]) retcode, out, err = self.mimetypes_cmd('--invalid')
with support.captured_stdout() as output: self.assertEqual(retcode, 2)
with self.assertRaises(SystemExit) as cm: self.assertEqual(out, '')
mimetypes._main() self.assertStartsWith(err, 'usage: ')
self.assertIn("Usage: mimetypes.py", output.getvalue())
self.assertEqual(cm.exception.code, 1)
def test_guess_extension(self): def test_guess_extension(self):
eq = self.assertEqual 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("-l", "-e", "image/jpg") retcode, out, err = self.mimetypes_cmd('-e', 'image/jpg')
eq(extension, ".jpg") self.assertEqual(retcode, 1)
self.assertEqual(out, '')
self.assertEqual(err, f'error: unknown type image/jpg{linesep}')
extension = self.mimetypes_cmd("-e", "image/jpg") retcode, out, err = self.mimetypes_cmd('-e', 'image/jpeg')
eq(extension, "I don't know anything about type image/jpg") self.assertEqual(retcode, 0)
self.assertEqual(out, f'.jpg{linesep}')
extension = self.mimetypes_cmd("-e", "image/jpeg") self.assertEqual(err, '')
eq(extension, ".jpg")
def test_guess_type(self): def test_guess_type(self):
eq = self.assertEqual 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, '')
type_info = self.mimetypes_cmd("-l", "foo.pic") @unittest.skipIf(
eq(type_info, "type: image/pict encoding: None") sys.platform == 'darwin',
'macOS lists common_types in mime.types thus making them always known'
type_info = self.mimetypes_cmd("foo.pic") )
eq(type_info, "I don't know anything about type foo.pic") 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__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -849,6 +849,7 @@ Oleg Höfling
Robert Hölzl Robert Hölzl
Stefan Hölzl Stefan Hölzl
Catalin Iacob Catalin Iacob
Oleg Iarygin
Mihai Ibanescu Mihai Ibanescu
Ali Ikinci Ali Ikinci
Aaron Iles Aaron Iles

View File

@ -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.