Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ create a ``.Trash-$UID`` directory.

For any other problem, ``OSError`` is raised.

Command-line usage
------------------

Use ``send2trash`` or ``python -m send2trash`` to trash files from the command line::

send2trash --verbose file1 file2

By default, the command stops at the first error. To attempt the remaining files
after an error, use ``--continue-on-error``::

send2trash --continue-on-error --verbose file1 file2

Errors are written to standard error. The command exits with status 1 if any
file could not be trashed, even when ``--continue-on-error`` is used. Verbose
output lists only successfully trashed files. This option does not change the
Python API.

.. _PyGObject: https://wiki.gnome.org/PyGObject
.. _GIO: https://developer.gnome.org/gio/
.. _trash specifications from freedesktop.org: http://freedesktop.org/wiki/Specifications/trash-spec/
11 changes: 10 additions & 1 deletion send2trash/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,25 @@ def main(args=None):
parser = ArgumentParser(description="Tool to send files to trash")
parser.add_argument("files", nargs="+")
parser.add_argument("-v", "--verbose", action="store_true", help="Print deleted files")
parser.add_argument(
"--continue-on-error", action="store_true", help="Continue with remaining files after an error (exit status is still 1)"
)
args = parser.parse_args(args)

failed = False
for filename in args.files:
try:
send2trash(filename)
if args.verbose:
print("Trashed «" + filename + "»")
except OSError as e:
print(str(e), file=sys.stderr)
sys.exit(1)
if not args.continue_on_error:
sys.exit(1)
failed = True

if failed:
sys.exit(1)


if __name__ == "__main__":
Expand Down
53 changes: 53 additions & 0 deletions tests/test_script_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,56 @@ def test_no_args(test_file):
pytest.raises(SystemExit, trash_main, [])
pytest.raises(SystemExit, trash_main, ["-v"])
assert op.exists(test_file) is True


@pytest.mark.parametrize("continue_on_error", [False, True])
def test_batch_success(monkeypatch, capsys, continue_on_error):
calls = []
monkeypatch.setattr("send2trash.__main__.send2trash", calls.append)
args = ["-v", "first", "second"]
if continue_on_error:
args.insert(0, "--continue-on-error")

trash_main(args)

assert calls == ["first", "second"]
captured = capsys.readouterr()
assert captured.out == "Trashed «first»\nTrashed «second»\n"
assert captured.err == ""


@pytest.mark.parametrize("continue_on_error", [False, True])
@pytest.mark.parametrize("failed_files", [("first",), ("second",), ("first", "second")])
def test_batch_errors(monkeypatch, capsys, continue_on_error, failed_files):
calls = []

def trash(filename):
calls.append(filename)
if filename in failed_files:
raise OSError("Cannot trash " + filename)

monkeypatch.setattr("send2trash.__main__.send2trash", trash)
files = ["first", "second", "third"]
args = ["-v"] + files
if continue_on_error:
args.insert(0, "--continue-on-error")

with pytest.raises(SystemExit) as exc:
trash_main(args)

assert exc.value.code == 1
expected_calls = files if continue_on_error else files[:files.index(failed_files[0]) + 1]
assert calls == expected_calls
captured = capsys.readouterr()
assert captured.out == "".join("Trashed «" + name + "»\n" for name in calls if name not in failed_files)
assert captured.err == "".join("Cannot trash " + name + "\n" for name in calls if name in failed_files)


def test_continue_on_error_does_not_hide_unexpected_errors(monkeypatch):
def trash(filename):
raise RuntimeError("unexpected error")

monkeypatch.setattr("send2trash.__main__.send2trash", trash)

with pytest.raises(RuntimeError, match="unexpected error"):
trash_main(["--continue-on-error", "first"])