diff --git a/README.md b/README.md index 4bd907a3..a5df8c09 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,20 @@ You can also combine the flag options: 'text/plain' ``` +## Command line + +Identify one or more files using the same libmagic installation as the Python API: + +```sh +python -m magic example.pdf +python -m magic --mime example.pdf image.jpg +``` + +Each result is printed as `filename: description` (or `filename: MIME type`). +Errors raised by file access or libmagic go to stderr; remaining files are +still inspected, and the command exits with status 1. Use `--` before a filename +that starts with a dash. This requires libmagic just like the Python API. + ## Installation The current stable version of python-magic is available on PyPI and diff --git a/magic/__main__.py b/magic/__main__.py new file mode 100644 index 00000000..c6b81941 --- /dev/null +++ b/magic/__main__.py @@ -0,0 +1,34 @@ +"""Identify files with ``python -m magic``.""" + +from __future__ import print_function # noqa: UP010 - Keep the advertised Python 2.7 syntax. + +import argparse +import sys + +from magic import MagicException, from_file + + +def main(argv=None): + parser = argparse.ArgumentParser( + prog="python -m magic", description="Identify file types using libmagic" + ) + parser.add_argument( + "--mime", action="store_true", help="print MIME types instead of descriptions" + ) + parser.add_argument("files", nargs="+", help="files to identify") + args = parser.parse_args(argv) + + status = 0 + for filename in args.files: + try: + result = from_file(filename, mime=args.mime) + except (OSError, MagicException) as error: + print("{}: {}".format(filename, error), file=sys.stderr) # noqa: UP032 + status = 1 + else: + print("{}: {}".format(filename, result)) # noqa: UP032 + return status + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/test/cli_test.py b/test/cli_test.py new file mode 100644 index 00000000..940356c2 --- /dev/null +++ b/test/cli_test.py @@ -0,0 +1,77 @@ +# Keep these tests compatible with the Python 2.7+ matrix in tox.ini. +# ruff: noqa: UP032 +import importlib +import os +import subprocess +import sys +from collections import namedtuple + +import magic + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PDF = os.path.join(ROOT, "test", "testdata", "test.pdf") +CLIResult = namedtuple("CLIResult", "returncode stdout stderr") + + +def run_cli(*args): + process = subprocess.Popen( + [sys.executable, "-m", "magic"] + list(args), + cwd=ROOT, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) + stdout, stderr = process.communicate() + return CLIResult(process.returncode, stdout, stderr) + + +def test_module_description(): + result = run_cli(PDF) + assert result.returncode == 0 + assert result.stdout == "{}: {}\n".format(PDF, magic.from_file(PDF)) + assert result.stderr == "" + + +def test_module_mime(): + result = run_cli("--mime", PDF) + assert result.returncode == 0 + assert result.stdout == "{}: application/pdf\n".format(PDF) + assert result.stderr == "" + + +def test_module_continues_after_missing_file(tmpdir): + missing = str(tmpdir.join("not found.pdf")) + result = run_cli(missing, PDF) + assert result.returncode == 1 + assert result.stdout == "{}: {}\n".format(PDF, magic.from_file(PDF)) + assert missing in result.stderr + assert "Traceback" not in result.stderr + + +def test_module_handles_unicode_file_name(tmpdir): + path = tmpdir.join(b"a unicode \xc3\xa9 file.txt".decode("utf-8")) + path.write_binary(b"plain text\n") + result = run_cli(str(path)) + assert result.returncode == 0 + assert result.stdout == "{}: {}\n".format(path, magic.from_file(str(path))) + + +def test_module_requires_file(): + result = run_cli() + assert result.returncode == 2 + assert "usage:" in result.stderr + assert "Traceback" not in result.stderr + + +def test_module_reports_libmagic_failure(monkeypatch, capsys): + cli = importlib.import_module("magic.__main__") + + def fail(filename, mime=False): + raise magic.MagicException("libmagic failed") + + monkeypatch.setattr(cli, "from_file", fail) + assert cli.main(["example.pdf"]) == 1 + stdout, stderr = capsys.readouterr() + assert stdout == "" + assert "example.pdf" in stderr + assert "libmagic failed" in stderr