Repository: peterbe/hashin

Issue:
Be kinder if the package is a requirements file
So many times I've typed something like this:

```bash
▶ hashin --dry-run -u requirements/default.txt
Can not combine the --update-all option with a list of packages.
```
when I meant to type `hashin --dry-run -u -r requirements/default.txt`

It reminds me of how `pip` deals with `pip install requirements.txt` and thinks `requirements.txt` is a package name on PyPI.

Discussion:

Patch:
diff --git a/hashin.py b/hashin.py
index a43390e..427079f 100755
--- a/hashin.py
+++ b/hashin.py
@@ -773,6 +773,23 @@ def main():
     parser = get_parser()
     args = parser.parse_args()
 
+    if (
+        args.update_all
+        and args.packages
+        and len(args.packages) == 1
+        and os.path.isfile(args.packages[0])
+        and args.packages[0].endswith(".txt")
+    ):
+        # It's totally common to make the mistake of using the `--update-all` flag
+        # and specifying the requirements file as the first argument. E.g.
+        #
+        #     $ hashin --update-all --interactive myproject/reqs.txt
+        #
+        # The user intention is clear any non-keyed flags get interpreted as a
+        # list of "packages". Let's fix that for the user.
+        args.requirements_file = args.packages[0]
+        args.packages = []
+
     if args.update_all:
         if args.packages:
             print(


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index e494a33..8c60ba0 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -182,6 +182,36 @@ def test_packages_and_update_all(capsys, mock_get_parser):
     )
 
 
+def test_packages_and_update_all_with_requirements_file(
+    capsys, mock_get_parser, tmpfile
+):
+    with tmpfile() as filename:
+        with open(filename, "w") as f:
+            f.write("")
+
+        def mock_parse_args(*a, **k):
+            return argparse.Namespace(
+                packages=[filename],  # Note!
+                algorithm="sha256",
+                python_version="3.8",
+                verbose=False,
+                include_prereleases=False,
+                dry_run=False,
+                update_all=True,
+                interactive=False,
+                synchronous=False,
+                index_url="anything",
+            )
+
+        mock_get_parser().parse_args.side_effect = mock_parse_args
+
+        error = hashin.main()
+        assert error == 0
+        # Because the requirements file is empty, the update-all command
+        # won't find anything to query the internet about so we don't
+        # need to mock murlopen in this test.
+
+
 def test_no_packages_and_not_update_all(capsys, mock_get_parser):
     def mock_parse_args(*a, **k):
         return argparse.Namespace(