diff -Nru trash-cli-0.23.2.13.2/.github/workflows/make-release.yml trash-cli-0.23.11.10/.github/workflows/make-release.yml --- trash-cli-0.23.2.13.2/.github/workflows/make-release.yml 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/.github/workflows/make-release.yml 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,31 @@ +name: Make Release + +on: push + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Set version + run: | + scripts/set-dev-version ${{ github.ref_name }} ${{ github.sha }} + + - name: Make release + run: scripts/test-sdist + + - name: version name + run: echo "version=$(python -c 'import setuptools; setuptools.setup()' --version)" >> "$GITHUB_OUTPUT" + id: version-name + + - name: Publish release + uses: actions/upload-artifact@v3 + with: + name: trash-cli-${{ steps.version-name.outputs.version }}.zip + path: dist/*.tar.gz + if-no-files-found: error + diff -Nru trash-cli-0.23.2.13.2/.github/workflows/run-tests.yml trash-cli-0.23.11.10/.github/workflows/run-tests.yml --- trash-cli-0.23.2.13.2/.github/workflows/run-tests.yml 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/.github/workflows/run-tests.yml 2023-11-10 07:15:04.000000000 +0000 @@ -15,10 +15,10 @@ strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: ['2.7', '3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: @@ -27,5 +27,8 @@ run: python -m pip install --upgrade pip - name: Install dependencies run: python -m pip install -r requirements-dev.txt -r requirements.txt + - name: Check Types + run: ./scripts/check-types + if: matrix.python-version != '2.7' - name: Run tests run: python -m pytest diff -Nru trash-cli-0.23.2.13.2/.github/workflows/test-sdist.yml trash-cli-0.23.11.10/.github/workflows/test-sdist.yml --- trash-cli-0.23.2.13.2/.github/workflows/test-sdist.yml 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/.github/workflows/test-sdist.yml 2023-11-10 07:15:04.000000000 +0000 @@ -13,10 +13,10 @@ strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: ['2.7', '3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff -Nru trash-cli-0.23.2.13.2/HISTORY.txt trash-cli-0.23.11.10/HISTORY.txt --- trash-cli-0.23.2.13.2/HISTORY.txt 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/HISTORY.txt 2023-11-10 07:15:04.000000000 +0000 @@ -2,7 +2,7 @@ - Made visible option -v/--verbose of trash-empty - Andrea Francia - Documented how completion works in README - Wu Zhenyu - Change: now if a file does not exists it does not tries to delete it using all available trash dirs - Andrea Francia - - Removed an errore that would occour on trash-put when HOME environment is not set - Andrea Francia + - Removed an error that would occur on trash-put when HOME environment is not set - Andrea Francia - Whitelisted fuse.glusterfs filesystem - Andrea Francia 0.22.10.4.4: - Revisited the trash-put log messages @@ -21,7 +21,7 @@ - also partitions mounted on /tmp with tmpfs will be considered physical volumes - `trash-list --all-users` to see trash from all users 0.22.8.21.16: - - Now supports p9 (WSL 2 volumes) as loation fro trash dirs + - Now supports p9 (WSL 2 volumes) as location for trash dirs - trash-list --volumes to list all the recognized volumes - trash-list --debug-volumes - Fix links to trash specification David Auer @@ -30,7 +30,7 @@ in the trash directory files pull request https://github.com/andreafrancia/trash-cli/pull/233 thanks to: https://github.com/jamescherti - - trash-empty do not list trahs directories that does not exist + - trash-empty do not list trash directories that does not exist pull request https://github.com/andreafrancia/trash-cli/pull/237 thanks to: https://github.com/jack1142 - Fix trash-empty not showing nfs mountpoints diff -Nru trash-cli-0.23.2.13.2/README.rst trash-cli-0.23.11.10/README.rst --- trash-cli-0.23.2.13.2/README.rst 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/README.rst 2023-11-10 07:15:04.000000000 +0000 @@ -165,8 +165,13 @@ Requirements: * Python 3 (Python 2.7 also work) + * pipx_ (optional, to install in a clean environment) -Installation command:: +If pipx is available:: + + pipx install trash-cli + +Alternatively, install with vanilla pip:: pip install trash-cli @@ -177,6 +182,10 @@ For uninstalling use:: + pipx uninstall trash-cli + +or:: + pip uninstall trash-cli Bleeding Edge (from sources) @@ -208,11 +217,19 @@ sudo pacman -S trash-cli +Fedora (dnf):: + + sudo dnf install trash-cli + Install shell completions ~~~~~~~~~~~~~~~~~~~~~~~~~ You need install by:: + pipx install 'trash-cli[completion]' + +or:: + pip install 'trash-cli[completion]' Then:: @@ -265,3 +282,4 @@ .. _简体中文: https://github.com/andreafrancia/trash-cli/blob/master/README_zh-CN.rst .. _project contributors: https://github.com/andreafrancia/trash-cli/graphs/contributors .. _JetBrains: https://jb.gg/OpenSource +.. _pipx: https://pypa.github.io/pipx/ diff -Nru trash-cli-0.23.2.13.2/README_zh-CN.rst trash-cli-0.23.11.10/README_zh-CN.rst --- trash-cli-0.23.2.13.2/README_zh-CN.rst 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/README_zh-CN.rst 2023-11-10 07:15:04.000000000 +0000 @@ -158,11 +158,14 @@ - Python 3 (Python 2.7 也可以) - pip (在 Debian 上用 `apt-get install python-pip` 来安装 pip) + - pipx_ (可选,推荐,安装在虚拟环境中以保护系统环境) 安装命令: :: + pipx install trash-cli + # 或者 pip install trash-cli 源码安装 @@ -194,6 +197,8 @@ :: + pipx uninstall trash-cli + # 或者 pip uninstall trash-cli 用包管理器安装 @@ -235,3 +240,4 @@ .. |Donate| image:: https://www.paypalobjects.com/en_GB/i/btn/btn_donate_SM.gif .. _Donate: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=93L6PYT4WBN5A .. _English: https://github.com/andreafrancia/trash-cli/blob/master/README.rst +.. _pipx: https://pypa.github.io/pipx diff -Nru trash-cli-0.23.2.13.2/debian/changelog trash-cli-0.23.11.10/debian/changelog --- trash-cli-0.23.2.13.2/debian/changelog 2023-07-31 05:37:42.000000000 +0000 +++ trash-cli-0.23.11.10/debian/changelog 2024-02-09 20:42:16.000000000 +0000 @@ -1,9 +1,15 @@ -trash-cli (0.23.2.13.2-1.1) unstable; urgency=medium +trash-cli (0.23.11.10-1) unstable; urgency=medium - * Non-maintainer upload. - * source only upload to enable migration (Closes: #1042721) + * New upstream version. + * Parameter '--' works again. Closes: #1050732. - -- Paul Gevers Mon, 31 Jul 2023 07:37:42 +0200 + -- Jonathan Dowland Fri, 09 Feb 2024 20:42:16 +0000 + +trash-cli (0.23.2.13.2-2) unstable; urgency=medium + + * Add python3-six to dependencies. Closes: #1061036. + + -- Jonathan Dowland Thu, 08 Feb 2024 21:57:31 +0000 trash-cli (0.23.2.13.2-1) unstable; urgency=medium diff -Nru trash-cli-0.23.2.13.2/debian/control trash-cli-0.23.11.10/debian/control --- trash-cli-0.23.2.13.2/debian/control 2023-07-31 05:37:42.000000000 +0000 +++ trash-cli-0.23.11.10/debian/control 2024-02-09 20:23:20.000000000 +0000 @@ -13,6 +13,7 @@ python3-psutil, python3-pytest, python3-setuptools, + python3-six, Standards-Version: 4.6.2 Homepage: https://github.com/andreafrancia/trash-cli Vcs-Git: https://salsa.debian.org/debian/trash-cli.git diff -Nru trash-cli-0.23.2.13.2/docs/how-to-build-and-upload-a-new-release.rst trash-cli-0.23.11.10/docs/how-to-build-and-upload-a-new-release.rst --- trash-cli-0.23.2.13.2/docs/how-to-build-and-upload-a-new-release.rst 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/docs/how-to-build-and-upload-a-new-release.rst 2023-11-10 07:15:04.000000000 +0000 @@ -5,6 +5,10 @@ scripts/bump +Launch GitHub Actions to check the release:: + + git push origin master + Load the version in an environment variable:: version="$(python -c 'import setuptools; setuptools.setup()' --version)" diff -Nru trash-cli-0.23.2.13.2/man/man1/trash-put.1 trash-cli-0.23.11.10/man/man1/trash-put.1 --- trash-cli-0.23.2.13.2/man/man1/trash-put.1 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/man/man1/trash-put.1 2023-11-10 07:15:04.000000000 +0000 @@ -39,6 +39,22 @@ compliant with the FreeDesktop.org Trash Specification. It remembers the name, original path, deletion date, and permissions of each trashed file. +.br + +Files trashed from the home partition will be in either $XDG_DATA_HOME/Trash +or ~/.local/share/Trash/ (if $XDG_DATA_HOME is not defined or empty). + +Files trashed from other partitions will be in one of the two places: + + - $top_dir/.Trash/$uid + - $top_dir/.Trash-$uid + +Where: + - $top_dir is the mount point of the volume containing the file to be removed + - $uid is the numeric ID of the deleting user + +The first option $top_dir/.Trash/$uid works only when .Trash dir has the sticky +bit set. The second option is used when the first is not viable. .SH "ARGUMENTS" .TP @@ -55,13 +71,13 @@ Show help message and exit. .IP "--trash-dir=TRASHDIR" -Use TRASHDIR as trash folder. +Use TRASHDIR as the trash folder. .IP "-v, --verbose" Explain what is being done. .IP "--version" -Show program's version number and exit. +Show the program's version number and exit. .SH "EXAMPLES" .nf diff -Nru trash-cli-0.23.2.13.2/man/man1/trash.1 trash-cli-0.23.11.10/man/man1/trash.1 --- trash-cli-0.23.2.13.2/man/man1/trash.1 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/man/man1/trash.1 2023-11-10 07:15:04.000000000 +0000 @@ -39,6 +39,22 @@ compliant with the FreeDesktop.org Trash Specification. It remembers the name, original path, deletion date, and permissions of each trashed file. +.br + +Files trashed from the home partition will be in either $XDG_DATA_HOME/Trash +or ~/.local/share/Trash/ (if $XDG_DATA_HOME is not defined or empty). + +Files trashed from other partitions will be in one of the two places: + + - $top_dir/.Trash/$uid + - $top_dir/.Trash-$uid + +Where: + - $top_dir is the mount point of the volume containing the file to be removed + - $uid is the numeric ID of the deleting user + +The first option $top_dir/.Trash/$uid works only when .Trash dir has the sticky +bit set. The second option is used when the first is not viable. .SH "ARGUMENTS" .TP @@ -55,13 +71,13 @@ Show help message and exit. .IP "--trash-dir=TRASHDIR" -Use TRASHDIR as trash folder. +Use TRASHDIR as the trash folder. .IP "-v, --verbose" Explain what is being done. .IP "--version" -Show program's version number and exit. +Show the program's version number and exit. .SH "EXAMPLES" .nf diff -Nru trash-cli-0.23.2.13.2/mypy.ini trash-cli-0.23.11.10/mypy.ini --- trash-cli-0.23.2.13.2/mypy.ini 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/mypy.ini 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,2 @@ +[mypy] +files = tests, trashcli diff -Nru trash-cli-0.23.2.13.2/requirements-dev.txt trash-cli-0.23.11.10/requirements-dev.txt --- trash-cli-0.23.2.13.2/requirements-dev.txt 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/requirements-dev.txt 2023-11-10 07:15:04.000000000 +0000 @@ -6,3 +6,6 @@ build flexmock parameterized +types-six +types-mock +types-psutil diff -Nru trash-cli-0.23.2.13.2/scripts/check-types trash-cli-0.23.11.10/scripts/check-types --- trash-cli-0.23.2.13.2/scripts/check-types 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/scripts/check-types 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/bash +[ -x "$(command -v mypy)" ] || pip install mypy +mypy trashcli tests diff -Nru trash-cli-0.23.2.13.2/scripts/set-dev-version trash-cli-0.23.11.10/scripts/set-dev-version --- trash-cli-0.23.2.13.2/scripts/set-dev-version 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/scripts/set-dev-version 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,30 @@ +#!/usr/bin/env python +from __future__ import print_function + +import argparse +import datetime +import os +import sys + +root = os.path.realpath(os.path.join(__file__, "..", "..")) +sys.path.insert(0, root) +from scripts import bump + +parser = argparse.ArgumentParser() +parser.add_argument('ref') +parser.add_argument('sha') +args = parser.parse_args() + +if "-" in args.ref: + print("Ref cannot contain '-': %s\n" % args.ref + + "The reason is because any '-' will be converted to '.' by setuptools " + "during the egg_info phase that will result in an error in " + "scripts/make-scripts because it will be not able to find the .tar.gz file", + file=sys.stderr) + sys.exit(1) +new_version = (bump.version_from_date(datetime.date.today()) + + '.dev0+git.%s.%s' % (args.ref, args.sha)) +version_file = os.path.join(root, 'trashcli', 'trash.py') +bump.save_new_version(new_version, version_file) + +os.system("cat %s" % version_file) diff -Nru trash-cli-0.23.2.13.2/scripts/test-sdist trash-cli-0.23.11.10/scripts/test-sdist --- trash-cli-0.23.2.13.2/scripts/test-sdist 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/scripts/test-sdist 2023-11-10 07:15:04.000000000 +0000 @@ -24,3 +24,9 @@ trash-rm --version trash-restore --version trash-empty --version + +touch foo bar +trash-put foo bar +trash-list +trash-rm bar +echo | trash-empty diff -Nru trash-cli-0.23.2.13.2/setup.cfg trash-cli-0.23.11.10/setup.cfg --- trash-cli-0.23.2.13.2/setup.cfg 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/setup.cfg 2023-11-10 07:15:04.000000000 +0000 @@ -14,9 +14,17 @@ trashcli trashcli.empty trashcli.lib + trashcli.list + trashcli.list.minor_actions trashcli.put + trashcli.put.core + trashcli.put.janitor_tools trashcli.put.fs trashcli.restore + trashcli.rm + trashcli.fstab + trashcli.parse_trashinfo + scripts = trash trash-put @@ -24,10 +32,12 @@ trash-restore trash-empty trash-rm + install_requires = psutil six - typing + typing; python_version < '3.8' + typing_extensions; python_version < '3.8' [options.extras_require] completion = diff -Nru trash-cli-0.23.2.13.2/tests/mock_dir_reader.py trash-cli-0.23.11.10/tests/mock_dir_reader.py --- trash-cli-0.23.2.13.2/tests/mock_dir_reader.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/mock_dir_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,13 +1,15 @@ import os -from trashcli.trash import DirReader +from typing import List + +from trashcli.lib.dir_reader import DirReader class MockDirReader(DirReader): def __init__(self): self.root = {} - def entries_if_dir_exists(self, path): # type: (str) -> list[str] + def entries_if_dir_exists(self, path): # type: (str) -> List[str] return list(self.pick_dir(path).keys()) def exists(self, path): # type: (str) -> bool diff -Nru trash-cli-0.23.2.13.2/tests/run_command.py trash-cli-0.23.11.10/tests/run_command.py --- trash-cli-0.23.2.13.2/tests/run_command.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/run_command.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,22 +1,77 @@ import os import subprocess import sys +from typing import List + +import pytest from scripts.make_scripts import script_path_for +from tests.support.help_reformatting import reformat_help_message +from tests.support.my_path import MyPath from trashcli import base_dir -def run_command(cwd, command, args=None, input='', env=None): - class Result: - def __init__(self, stdout, stderr, exit_code): - self.stdout = stdout - self.stderr = stderr - self.exit_code = exit_code - self.all = [stdout, stderr, exit_code] +class CmdResult: + def __init__(self, + stdout, # type: str + stderr, # type: str + exit_code, # type: int + ): # (...) -> None + self.stdout = stdout + self.stderr = stderr + self.exit_code = exit_code + self.all = [stdout, stderr, exit_code] + + def __str__(self): + return str(self.all) + + def output(self): + return self._format([self.stdout, self.stderr]) + + def last_line_of_stderr(self): + return last_line_of(self.stderr) + + def last_line_of_stdout(self): + return last_line_of(self.stdout) + + def reformatted_help(self): + return reformat_help_message(self.stdout) + + @staticmethod + def _format(outs): + outs = [out for out in outs if out != ""] + return "".join([out.rstrip("\n") + "\n" for out in outs]) + + def clean_vol_and_grep(self, + pattern, # type: str + fake_vol, # type: MyPath + ): # type: (...) -> List[str] + matching_lines = self._grep(self.stderr_lines(), pattern) + return self._replace(fake_vol, "/vol", matching_lines) + + @staticmethod + def _grep(lines, pattern): # type: (List[str], str) -> List[str] + return [line for line in lines if pattern in line] + + def clean_temp_dir(self, temp_dir): # type: (MyPath) -> List[str] + return self._replace(temp_dir, "", self.stderr_lines()) + + def clean_tmp_and_grep(self, temp_dir, + pattern): # type: (MyPath, str) -> List[str] + return self._grep(self.clean_temp_dir(temp_dir), pattern) + + def stderr_lines(self): # type: () -> List[str] + return self.stderr.splitlines() + + @staticmethod + def _replace(pattern, # type: str + replacement, # type: str + lines, # type: List[str] + ): # type: (...) -> List[str] + return [line.replace(pattern, replacement) for line in lines] - def __str__(self): - return str(self.all) +def run_command(cwd, command, args=None, input='', env=None): if env is None: env = {} if args is None: @@ -31,13 +86,16 @@ env=merge_dicts(os.environ, env)) stdout, stderr = process.communicate(input=input.encode('utf-8')) - return Result(stdout.decode('utf-8'), - stderr.decode('utf-8'), - process.returncode) + return CmdResult(stdout.decode('utf-8'), + stderr.decode('utf-8'), + process.returncode) -def normalize_options(help_message): - return help_message.replace('optional arguments', 'options') +@pytest.fixture +def temp_dir(): + temp_dir = MyPath.make_temp_dir() + yield temp_dir + temp_dir.clean_up() def merge_dicts(x, y): diff -Nru trash-cli-0.23.2.13.2/tests/support/asserts/__init__.py trash-cli-0.23.11.10/tests/support/asserts/__init__.py --- trash-cli-0.23.2.13.2/tests/support/asserts/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/asserts/__init__.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,28 @@ +import unittest + + +def assert_equals_with_unidiff(expected, actual): + def unidiff(expected, actual): + import difflib + expected = expected.splitlines(1) + actual = actual.splitlines(1) + + diff = difflib.unified_diff(expected, actual, + fromfile='Expected', tofile='Actual', + lineterm='\n', n=10) + + return ''.join(diff) + + assert expected == actual, ("\n" + "Expected:%s\n" % repr(expected) + + " Actual:%s\n" % repr(actual) + + unidiff(expected, actual)) + + +def assert_starts_with(actual, expected): + class Dummy(unittest.TestCase): + def nop(self): + pass + + _t = Dummy('nop') + _t.assertEqual(actual[:len(expected)], expected) diff -Nru trash-cli-0.23.2.13.2/tests/support/asserts/assert_that.py trash-cli-0.23.11.10/tests/support/asserts/assert_that.py --- trash-cli-0.23.2.13.2/tests/support/asserts/assert_that.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/asserts/assert_that.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,2 @@ +def assert_that(value, matcher): + assert matcher.matches(value), matcher.describe_mismatch(value) diff -Nru trash-cli-0.23.2.13.2/tests/support/asserts.py trash-cli-0.23.11.10/tests/support/asserts.py --- trash-cli-0.23.2.13.2/tests/support/asserts.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/asserts.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -from textwrap import dedent - - -def assert_line_in_text(line, text): - assert line in text.splitlines(), dedent('''\ - Line not found in text - Line: - - %s - - Text: - - --- - %s---''') % (repr(line), text) - - -def assert_equals_with_unidiff(expected, actual): - def unidiff(expected, actual): - import difflib - expected = expected.splitlines(1) - actual = actual.splitlines(1) - - diff = difflib.unified_diff(expected, actual, - fromfile='Expected', tofile='Actual', - lineterm='\n', n=10) - - return ''.join(diff) - - assert expected == actual, ("\n" - "Expected:%s\n" % repr(expected) + - " Actual:%s\n" % repr(actual) + - unidiff(expected, actual)) diff -Nru trash-cli-0.23.2.13.2/tests/support/fake_is_mount.py trash-cli-0.23.11.10/tests/support/fake_is_mount.py --- trash-cli-0.23.2.13.2/tests/support/fake_is_mount.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/fake_is_mount.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,23 @@ +import os +from typing import List + + +class FakeIsMount: + def __init__(self, + mount_points, # type: List[str] + ): + self.mount_points = mount_points + + def is_mount(self, path): + if path == '/': + return True + path = os.path.normpath(path) + if path in self.mount_points_list(): + return True + return False + + def mount_points_list(self): + return set(['/'] + self.mount_points) + + def add_mount_point(self, path): + self.mount_points.append(path) diff -Nru trash-cli-0.23.2.13.2/tests/support/fake_volume_of.py trash-cli-0.23.11.10/tests/support/fake_volume_of.py --- trash-cli-0.23.2.13.2/tests/support/fake_volume_of.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/fake_volume_of.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,21 @@ +import os +from typing import List, Callable + +from tests.support.fake_is_mount import FakeIsMount +from trashcli.fstab.volume_of import VolumeOf, VolumeOfImpl + + +def fake_volume_of(volumes): # type: (List[str]) -> VolumeOf + return VolumeOfImpl(FakeIsMount(volumes), os.path.normpath) + + +def volume_of_stub(func=lambda x: "volume_of %s" % x): # type: (Callable[[str], str]) -> VolumeOf + return _FakeVolumeOf(func) + + +class _FakeVolumeOf(VolumeOf): + def __init__(self, volume_of_impl): + self.volume_of_impl = volume_of_impl + + def volume_of(self, path): + return self.volume_of_impl(path) diff -Nru trash-cli-0.23.2.13.2/tests/support/help_reformatting.py trash-cli-0.23.11.10/tests/support/help_reformatting.py --- trash-cli-0.23.2.13.2/tests/support/help_reformatting.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/help_reformatting.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,35 @@ +def reformat_help_message(help_message): # type: (str) -> str + return _collapse_usage_paragraph(_normalize_options(help_message)) + + +def _normalize_options(help_message): # type: (str) -> str + return help_message.replace('optional arguments', 'options') + + +def _collapse_usage_paragraph(help_message): # type: (str) -> str + paragraphs = split_paragraphs(help_message) + return '\n'.join( + [normalize_spaces(paragraphs[0]) + "\n"] + + paragraphs[1:]) + + +def normalize_spaces(text): # type: (str) -> str + return " ".join(text.split()) + + +def split_paragraphs(text): + paragraphs = [] + par = '' + for line in text.splitlines(True): + if _is_empty_line(line): + paragraphs.append(par) + par = '' + else: + par += line + + paragraphs.append(par) + return paragraphs + + +def _is_empty_line(line): + return '' == line.strip() diff -Nru trash-cli-0.23.2.13.2/tests/support/sort_lines.py trash-cli-0.23.11.10/tests/support/sort_lines.py --- trash-cli-0.23.2.13.2/tests/support/sort_lines.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/sort_lines.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,2 @@ +def sort_lines(lines): + return "".join(sorted(lines.splitlines(True))) diff -Nru trash-cli-0.23.2.13.2/tests/support/volumes_mock.py trash-cli-0.23.11.10/tests/support/volumes_mock.py --- trash-cli-0.23.2.13.2/tests/support/volumes_mock.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/support/volumes_mock.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -from mock import Mock - -from trashcli.fstab import Volumes - - -def volumes_mock(func = lambda x: "volume_of %s" % x): - volumes = Mock(spec=Volumes) - volumes.volume_of = func - return volumes diff -Nru trash-cli-0.23.2.13.2/tests/test_candidate_shrink_user.py trash-cli-0.23.11.10/tests/test_candidate_shrink_user.py --- trash-cli-0.23.2.13.2/tests/test_candidate_shrink_user.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_candidate_shrink_user.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,6 @@ import unittest -from trashcli.put.candidate import Candidate +from trashcli.put.core.candidate import Candidate class TestCandidateShrinkUser(unittest.TestCase): diff -Nru trash-cli-0.23.2.13.2/tests/test_empty/empty_cmd/test_empty_cmd.py trash-cli-0.23.11.10/tests/test_empty/empty_cmd/test_empty_cmd.py --- trash-cli-0.23.2.13.2/tests/test_empty/empty_cmd/test_empty_cmd.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_empty/empty_cmd/test_empty_cmd.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,18 +1,18 @@ # Copyright (C) 2011-2022 Andrea Francia Bereguardo(PV) Italy import unittest - -from six import StringIO -from tests.mock_dir_reader import MockDirReader -from tests.support.volumes_mock import volumes_mock from typing import cast from flexmock import flexmock from mock import Mock, call -from trashcli.empty.delete_according_date import ContentReader +from six import StringIO + +from tests.mock_dir_reader import MockDirReader +from tests.support.fake_volume_of import volume_of_stub +from trashcli.empty.delete_according_date import ContentsOf from trashcli.empty.empty_cmd import EmptyCmd from trashcli.empty.existing_file_remover import ExistingFileRemover -from trashcli.fstab import VolumesListing -from trashcli.trash import DirReader +from trashcli.fstab.volume_listing import VolumesListing +from trashcli.lib.dir_reader import DirReader from trashcli.trash_dirs_scanner import TopTrashDirRules @@ -21,7 +21,7 @@ self.volumes_listing = Mock(spec=VolumesListing) self.file_reader = flexmock(TopTrashDirRules.Reader) self.file_remover = Mock(spec=ExistingFileRemover) - self.content_reader = flexmock(ContentReader) + self.content_reader = flexmock(ContentsOf) self.dir_reader = MockDirReader() self.err = StringIO() self.out = StringIO() @@ -34,10 +34,10 @@ now=None, file_reader=cast(TopTrashDirRules.Reader, self.file_reader), file_remover=cast(ExistingFileRemover, self.file_remover), - content_reader=cast(ContentReader, self.content_reader), + content_reader=cast(ContentsOf, self.content_reader), dir_reader=cast(DirReader, self.dir_reader), version='unused', - volumes=volumes_mock() + volumes=volume_of_stub() ) def test(self): diff -Nru trash-cli-0.23.2.13.2/tests/test_empty/empty_cmd/test_empty_cmd_fs.py trash-cli-0.23.11.10/tests/test_empty/empty_cmd/test_empty_cmd_fs.py --- trash-cli-0.23.2.13.2/tests/test_empty/empty_cmd/test_empty_cmd_fs.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_empty/empty_cmd/test_empty_cmd_fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,21 +1,20 @@ # Copyright (C) 2011-2022 Andrea Francia Bereguardo(PV) Italy import unittest +import pytest +from mock import Mock from six import StringIO + +from tests.support.fake_volume_of import volume_of_stub from tests.support.files import make_unreadable_dir, make_readable -from tests.support.volumes_mock import volumes_mock from tests.support.my_path import MyPath - -import pytest -from mock import Mock from trashcli.empty.empty_cmd import EmptyCmd from trashcli.empty.existing_file_remover import ExistingFileRemover -from trashcli.fs import ( - FileSystemContentReader, - FileSystemDirReader, - TopTrashDirRulesFileSystemReader, -) -from trashcli.fstab import VolumesListing +from trashcli.empty.file_system_dir_reader import FileSystemDirReader +from trashcli.empty.main import FileSystemContentReader +from trashcli.empty.top_trash_dir_rules_file_system_reader import \ + RealTopTrashDirRulesReader +from trashcli.fstab.volume_listing import VolumesListing @pytest.mark.slow @@ -33,12 +32,12 @@ err=self.err, volumes_listing=self.volumes_listing, now=None, - file_reader=TopTrashDirRulesFileSystemReader(), + file_reader=RealTopTrashDirRulesReader(), file_remover=ExistingFileRemover(), content_reader=FileSystemContentReader(), dir_reader=FileSystemDirReader(), version='unused', - volumes=volumes_mock() + volumes=volume_of_stub() ) def test_trash_empty_will_skip_unreadable_dir(self): diff -Nru trash-cli-0.23.2.13.2/tests/test_empty/empty_cmd/test_empty_cmd_with_multiple_volumes_fs.py trash-cli-0.23.11.10/tests/test_empty/empty_cmd/test_empty_cmd_with_multiple_volumes_fs.py --- trash-cli-0.23.2.13.2/tests/test_empty/empty_cmd/test_empty_cmd_with_multiple_volumes_fs.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_empty/empty_cmd/test_empty_cmd_with_multiple_volumes_fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -2,21 +2,20 @@ import os import unittest +from mock import Mock from six import StringIO + +from tests.support.fake_volume_of import volume_of_stub from tests.support.files import make_empty_file, require_empty_dir, make_dirs, \ set_sticky_bit -from tests.support.volumes_mock import volumes_mock from tests.support.my_path import MyPath - -from mock import Mock from trashcli.empty.empty_cmd import EmptyCmd from trashcli.empty.existing_file_remover import ExistingFileRemover -from trashcli.fs import ( - FileSystemContentReader, - FileSystemDirReader, - TopTrashDirRulesFileSystemReader, -) -from trashcli.fstab import VolumesListing +from trashcli.empty.file_system_dir_reader import FileSystemDirReader +from trashcli.empty.main import FileSystemContentReader +from trashcli.empty.top_trash_dir_rules_file_system_reader import \ + RealTopTrashDirRulesReader +from trashcli.fstab.volume_listing import VolumesListing class TestEmptyCmdWithMultipleVolumesFs(unittest.TestCase): @@ -33,12 +32,12 @@ err=StringIO(), volumes_listing=self.volumes_listing, now=None, - file_reader=TopTrashDirRulesFileSystemReader(), + file_reader=RealTopTrashDirRulesReader(), file_remover=ExistingFileRemover(), content_reader=FileSystemContentReader(), dir_reader=FileSystemDirReader(), version='unused', - volumes=volumes_mock(), + volumes=volume_of_stub(), ) def test_it_removes_trashinfos_from_method_1_dir(self): diff -Nru trash-cli-0.23.2.13.2/tests/test_empty/test_clock.py trash-cli-0.23.11.10/tests/test_empty/test_clock.py --- trash-cli-0.23.2.13.2/tests/test_empty/test_clock.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_empty/test_clock.py 2023-11-10 07:15:04.000000000 +0000 @@ -2,7 +2,7 @@ import unittest from mock import Mock, call -from trashcli.trash import Clock +from trashcli.empty.clock import Clock class TestClock(unittest.TestCase): diff -Nru trash-cli-0.23.2.13.2/tests/test_empty/test_empty_end_to_end.py trash-cli-0.23.11.10/tests/test_empty/test_empty_end_to_end.py --- trash-cli-0.23.2.13.2/tests/test_empty/test_empty_end_to_end.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_empty/test_empty_end_to_end.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,9 +1,8 @@ import unittest from trashcli import trash - from .. import run_command -from ..run_command import normalize_options +from ..support.help_reformatting import reformat_help_message from ..support.my_path import MyPath @@ -14,7 +13,7 @@ def test_help(self): result = run_command.run_command(self.tmp_dir, "trash-empty", ['--help']) - self.assertEqual(["""\ + self.assertEqual([reformat_help_message("""\ usage: trash-empty [-h] [--print-completion {bash,zsh,tcsh}] [--version] [-v] [--trash-dir TRASH_DIR] [--all-users] [-i] [-f] [--dry-run] [days] @@ -38,8 +37,8 @@ --dry-run show which files would have been removed Report bugs to https://github.com/andreafrancia/trash-cli/issues -""", '', 0], - [normalize_options(result.stdout), +"""), '', 0], + [result.reformatted_help(), result.stderr, result.exit_code]) diff -Nru trash-cli-0.23.2.13.2/tests/test_empty/test_make_parser.py trash-cli-0.23.11.10/tests/test_empty/test_make_parser.py --- trash-cli-0.23.2.13.2/tests/test_empty/test_make_parser.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_empty/test_make_parser.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,4 +1,5 @@ import unittest +from typing import Union from trashcli.empty.parser import Parser @@ -8,37 +9,43 @@ self.parser = Parser() def test(self): - parsed = self.parser.parse(default_is_interactive=False, - args=['--trash-dir=foo']) + parsed = self.parse(args=['--trash-dir=foo']) assert ['foo'] == parsed.user_specified_trash_dirs def test_non_interactive_default_is_non_interactive(self): - parsed = self.parser.parse(default_is_interactive=False, + parsed = self.parse(default_is_interactive=False, args=[]) assert parsed.interactive == False def test_interactive_default_is_interactive(self): - parsed = self.parser.parse(default_is_interactive=True, + parsed = self.parse(default_is_interactive=True, args=[]) assert parsed.interactive == True def test_interactive_made_non_interactive(self): - parsed = self.parser.parse(default_is_interactive=True, - args=['-f']) + parsed = self.parse(default_is_interactive=True, args=['-f']) assert parsed.interactive == False def test_dry_run(self): - parsed = self.parser.parse(default_is_interactive=True, - args=['--dry-run']) + parsed = self.parse(args=['--dry-run']) assert parsed.dry_run == True def test_dry_run_default(self): - parsed = self.parser.parse(default_is_interactive=True, - args=[]) + parsed = self.parse(args=[]) assert parsed.dry_run == False + + def parse(self, + args, + default_is_interactive="ignored", # type: Union[bool, str] + ): + return self.parser.parse(default_is_interactive=default_is_interactive, + args=args, + environ={}, + uid=0, + argv0="ignored",) diff -Nru trash-cli-0.23.2.13.2/tests/test_empty/test_user.py trash-cli-0.23.11.10/tests/test_empty/test_user.py --- trash-cli-0.23.2.13.2/tests/test_empty/test_user.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_empty/test_user.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,31 +1,33 @@ import unittest from mock import Mock, call + from trashcli.empty.user import User +from trashcli.lib.my_input import HardCodedInput class TestUser(unittest.TestCase): def setUp(self): self.prepare_output_message = Mock(spec=[]) - self.input = Mock(spec=[]) + self.input = HardCodedInput() self.parse_reply = Mock(spec=[]) self.user = User(self.prepare_output_message, self.input, self.parse_reply) def test(self): self.prepare_output_message.return_value = 'output_msg' self.parse_reply.return_value = 'result' - self.input.return_value = 'reply' + self.input.set_reply('reply') result = self.user.do_you_wanna_empty_trash_dirs(['trash_dirs']) assert [ result, - self.input.mock_calls, + self.input.used_prompt, self.prepare_output_message.mock_calls, self.parse_reply.mock_calls, ] == [ 'result', - [call('output_msg')], + 'output_msg', [call(['trash_dirs'])], [call('reply')] ] diff -Nru trash-cli-0.23.2.13.2/tests/test_fake_fstab.py trash-cli-0.23.11.10/tests/test_fake_fstab.py --- trash-cli-0.23.2.13.2/tests/test_fake_fstab.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_fake_fstab.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,20 +1,20 @@ import unittest -from trashcli.fstab import create_fake_volume_of +from tests.support.fake_volume_of import fake_volume_of class TestFakeFstab(unittest.TestCase): def test_default(self): - self.volumes = create_fake_volume_of([]) + self.volumes = fake_volume_of([]) assert ["/"] == self.filter_only_mount_points("/") def test_it_should_accept_fake_mount_points(self): - self.volumes = create_fake_volume_of(['/fake']) + self.volumes = fake_volume_of(['/fake']) assert ['/', '/fake'] == self.filter_only_mount_points('/', '/fake') def test_something(self): - volumes = create_fake_volume_of(['/fake']) + volumes = fake_volume_of(['/fake']) assert '/fake' == volumes.volume_of('/fake/foo') def filter_only_mount_points(self, *supposed_mount_points): diff -Nru trash-cli-0.23.2.13.2/tests/test_fake_ismount.py trash-cli-0.23.11.10/tests/test_fake_ismount.py --- trash-cli-0.23.2.13.2/tests/test_fake_ismount.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_fake_ismount.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,6 @@ import unittest -from trashcli.fstab import FakeIsMount +from tests.support.fake_is_mount import FakeIsMount class TestOnDefault(unittest.TestCase): diff -Nru trash-cli-0.23.2.13.2/tests/test_file_remover.py trash-cli-0.23.11.10/tests/test_file_remover.py --- trash-cli-0.23.2.13.2/tests/test_file_remover.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_file_remover.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -import unittest - -from trashcli.fs import FileRemover - - -try: - FileNotFoundError -except NameError: - FileNotFoundError = OSError # python 2 - - -class TestFileRemover(unittest.TestCase): - def test_remove_file_fails_when_file_does_not_exists(self): - file_remover = FileRemover() - self.assertRaises(FileNotFoundError, file_remover.remove_file, - '/non/existing/path') diff -Nru trash-cli-0.23.2.13.2/tests/test_files.py trash-cli-0.23.11.10/tests/test_files.py --- trash-cli-0.23.2.13.2/tests/test_files.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_files.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -import errno -import os -import shutil -import unittest - -import pytest -from trashcli.fs import FileRemover, read_file - -from .support.files import make_unreadable_file, make_unreadable_dir, \ - make_readable -from .support.my_path import MyPath - - -@pytest.mark.slow -class Test_make_unreadable_file(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - - def test(self): - path = self.tmp_dir / "unreadable" - make_unreadable_file(self.tmp_dir / "unreadable") - with self.assertRaises(IOError): - read_file(path) - - def tearDown(self): - self.tmp_dir.clean_up() - - -class Test_make_unreadable_dir(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - self.unreadable_dir = self.tmp_dir / 'unreadable-dir' - - make_unreadable_dir(self.unreadable_dir) - - def test_the_directory_has_been_created(self): - assert os.path.exists(self.unreadable_dir) - - def test_and_can_not_be_removed(self): - try: - FileRemover().remove_file(self.unreadable_dir) - self.fail() - except OSError as e: - self.assertEqual(errno.errorcode[e.errno], 'EACCES') - - def tearDown(self): - make_readable(self.unreadable_dir) - shutil.rmtree(self.unreadable_dir) - self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_filesystem.py trash-cli-0.23.11.10/tests/test_filesystem.py --- trash-cli-0.23.2.13.2/tests/test_filesystem.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_filesystem.py 2023-11-10 07:15:04.000000000 +0000 @@ -4,8 +4,8 @@ import unittest import pytest -from trashcli.fs import has_sticky_bit, is_sticky_dir, mkdirs +from trashcli.fs import has_sticky_bit, mkdirs, is_sticky_dir from .support.files import make_empty_file, set_sticky_bit, unset_sticky_bit from .support.my_path import MyPath @@ -17,20 +17,17 @@ self.temp_dir = MyPath.make_temp_dir() def test_mkdirs_with_default_mode(self): - mkdirs(self.temp_dir / "test-dir/sub-dir") assert os.path.isdir(self.temp_dir / "test-dir/sub-dir") def test_has_sticky_bit_returns_true(self): - make_empty_file(self.temp_dir / "sticky") set_sticky_bit(self.temp_dir / "sticky") assert has_sticky_bit(self.temp_dir / 'sticky') def test_has_sticky_bit_returns_false(self): - make_empty_file(self.temp_dir / "non-sticky") set_sticky_bit(self.temp_dir / "non-sticky") unset_sticky_bit(self.temp_dir / "non-sticky") diff -Nru trash-cli-0.23.2.13.2/tests/test_fstab/test_volumes_listing.py trash-cli-0.23.11.10/tests/test_fstab/test_volumes_listing.py --- trash-cli-0.23.2.13.2/tests/test_fstab/test_volumes_listing.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_fstab/test_volumes_listing.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,12 +1,12 @@ import unittest -from trashcli.fstab import VolumesListing +from trashcli.fstab.mount_points_listing import FakeMountPointsListing +from trashcli.fstab.volume_listing import VolumesListingImpl -class TestVolumesListing(unittest.TestCase): +class TestVolumesListingImpl(unittest.TestCase): def setUp(self): - self.os_mount_points = lambda: ['/os-vol1', '/os-vol2'] - self.volumes_listing = VolumesListing(self.os_mount_points) + self.volumes_listing = VolumesListingImpl(FakeMountPointsListing(['/os-vol1', '/os-vol2'])) def test_os_mount_points(self): result = self.volumes_listing.list_volumes({}) diff -Nru trash-cli-0.23.2.13.2/tests/test_generate_scripts.py trash-cli-0.23.11.10/tests/test_generate_scripts.py --- trash-cli-0.23.2.13.2/tests/test_generate_scripts.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_generate_scripts.py 2023-11-10 07:15:04.000000000 +0000 @@ -21,10 +21,10 @@ scripts.add_script('trash', 'trashcli.put.main', 'main') scripts.add_script('trash-put', 'trashcli.put.main', 'main') - scripts.add_script('trash-list', 'trashcli.list', 'main') - scripts.add_script('trash-restore', 'trashcli.restore', 'main') + scripts.add_script('trash-list', 'trashcli.list.main', 'main') + scripts.add_script('trash-restore', 'trashcli.restore.main', 'main') scripts.add_script('trash-empty', 'trashcli.empty.main', 'main') - scripts.add_script('trash-rm', 'trashcli.rm', 'main') + scripts.add_script('trash-rm', 'trashcli.rm.main', 'main') self.assertEqual(['trash', 'trash-put', diff -Nru trash-cli-0.23.2.13.2/tests/test_list/cmd/setup.py trash-cli-0.23.11.10/tests/test_list/cmd/setup.py --- trash-cli-0.23.2.13.2/tests/test_list/cmd/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/cmd/setup.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,22 @@ +# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy +import unittest + +import pytest + +from tests.test_list.cmd.support import TrashListUser + +from tests.support.files import require_empty_dir +from tests.support.my_path import MyPath + +@pytest.mark.slow +class Setup(unittest.TestCase): + def setUp(self): + self.temp_dir = MyPath.make_temp_dir() + self.xdg_data_home = MyPath.make_temp_dir() + self.top_dir = self.temp_dir / "topdir" + require_empty_dir(self.top_dir) + self.user = TrashListUser(self.xdg_data_home) + + def tearDown(self): + self.xdg_data_home.clean_up() + self.temp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_list/cmd/support.py trash-cli-0.23.11.10/tests/test_list/cmd/support.py --- trash-cli-0.23.2.13.2/tests/test_list/cmd/support.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/cmd/support.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,63 @@ +import os + +from mock import Mock + +from tests.fake_trash_dir import FakeTrashDir +from tests.output_collector import OutputCollector +from tests.support.fake_volume_of import volume_of_stub +from trashcli.empty.main import FileSystemContentReader +from trashcli.empty.top_trash_dir_rules_file_system_reader import \ + RealTopTrashDirRulesReader +from trashcli.file_system_reader import FileSystemReader +from trashcli.fstab.volume_listing import VolumesListing +from trashcli.lib.dir_reader import RealDirReader +from trashcli.list.main import ListCmd + + +class TrashListUser: + def __init__(self, xdg_data_home): + self.stdout = OutputCollector() + self.stderr = OutputCollector() + self.environ = {'XDG_DATA_HOME': xdg_data_home} + self.fake_uid = None + self.volumes = [] + trash_dir = os.path.join(xdg_data_home, "Trash") + self.home_trashdir = FakeTrashDir(trash_dir) + self.version = None + + def run_trash_list(self, *args): + self.run('trash-list', *args) + + def run(self, *argv): + file_reader = FileSystemReader() + file_reader.list_volumes = lambda: self.volumes + dir_reader = file_reader + volumes_listing = Mock(spec=VolumesListing) + volumes_listing.list_volumes.return_value = self.volumes + ListCmd( + out=self.stdout, + err=self.stderr, + environ=self.environ, + volumes_listing=volumes_listing, + uid=self.fake_uid, + volumes=volume_of_stub(), + dir_reader=RealDirReader(), + file_reader=RealTopTrashDirRulesReader(), + content_reader=FileSystemContentReader(), + version=self.version + ).run(argv) + + def set_fake_uid(self, uid): + self.fake_uid = uid + + def add_volume(self, mount_point): + self.volumes.append(mount_point) + + def error(self): + return self.stderr.getvalue() + + def output(self): + return self.stdout.getvalue() + + def set_version(self, version): + self.version = version diff -Nru trash-cli-0.23.2.13.2/tests/test_list/cmd/test_alternate_trash_dir.py trash-cli-0.23.11.10/tests/test_list/cmd/test_alternate_trash_dir.py --- trash-cli-0.23.2.13.2/tests/test_list/cmd/test_alternate_trash_dir.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/cmd/test_alternate_trash_dir.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,22 @@ +# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy +from datetime import datetime + +from tests.test_list.cmd.setup import Setup + +from tests.support.asserts import assert_equals_with_unidiff +from tests.fake_trash_dir import FakeTrashDir + + +class TestAlternateTrashDir(Setup): + + def test_should_list_contents_of_alternate_trashdir(self): + self.user.set_fake_uid(123) + self.user.add_volume(self.top_dir) + self.top_trashdir2 = FakeTrashDir(self.top_dir / '.Trash-123') + self.top_trashdir2.add_trashinfo2('file', datetime(2000, 1, 1, 0, 0, 0)) + + self.user.run_trash_list() + + assert_equals_with_unidiff("2000-01-01 00:00:00 %s/file\n" % + self.top_dir, + self.user.output()) diff -Nru trash-cli-0.23.2.13.2/tests/test_list/cmd/test_end_to_end_list.py trash-cli-0.23.11.10/tests/test_list/cmd/test_end_to_end_list.py --- trash-cli-0.23.2.13.2/tests/test_list/cmd/test_end_to_end_list.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/cmd/test_end_to_end_list.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,87 @@ +import datetime +import unittest + +import pytest + +from tests import run_command +from tests.fake_trash_dir import FakeTrashDir +from tests.support.help_reformatting import reformat_help_message +from tests.support.my_path import MyPath + + +@pytest.mark.slow +class TestEndToEndList(unittest.TestCase): + def setUp(self): + self.temp_dir = MyPath.make_temp_dir() + self.trash_dir = self.temp_dir / 'trash-dir' + self.fake_trash_dir = FakeTrashDir(self.trash_dir) + + def test_list(self): + self.fake_trash_dir.add_trashinfo2("/file1", + datetime.datetime(2000, 1, 1, 0, 0, + 1)) + self.fake_trash_dir.add_trashinfo2("/file2", + datetime.datetime(2000, 1, 1, 0, 0, + 1)) + + result = run_command.run_command(self.temp_dir, "trash-list", + ['--trash-dir', self.trash_dir]) + + assert [ + '2000-01-01 00:00:01 /file1', + '2000-01-01 00:00:01 /file2', + ] == sorted(result.stdout.splitlines()) + + def test_list_trash_dirs(self): + result = run_command.run_command( + self.temp_dir, "trash-list", + ['--trash-dirs', '--trash-dir=/home/user/.local/share/Trash']) + assert (result.stderr, + sorted(result.stdout.splitlines()), result.exit_code) == ( + '', [ + '/home/user/.local/share/Trash' + ], 0) + + def test_list_with_paths(self): + self.fake_trash_dir.add_trashinfo3("base1", "/file1", + datetime.datetime(2000, 1, 1, 0, 0, + 1)) + self.fake_trash_dir.add_trashinfo3("base2", "/file2", + datetime.datetime(2000, 1, 1, 0, 0, + 1)) + + result = run_command.run_command(self.temp_dir, "trash-list", + ['--trash-dir', self.trash_dir, + '--files']) + + assert ('', [ + '2000-01-01 00:00:01 /file1 -> %s/files/base1' % self.trash_dir, + '2000-01-01 00:00:01 /file2 -> %s/files/base2' % self.trash_dir, + ]) == (result.stderr, sorted(result.stdout.splitlines())) + + def test_help(self): + result = run_command.run_command(self.temp_dir, "trash-list", ['--help']) + + self.assertEqual(reformat_help_message("""\ +usage: trash-list [-h] [--print-completion {bash,zsh,tcsh}] [--version] + [--volumes] [--trash-dirs] [--trash-dir TRASH_DIRS] + [--all-users] + +List trashed files + +options: + -h, --help show this help message and exit + --print-completion {bash,zsh,tcsh} + print shell completion script + --version show program's version number and exit + --volumes list volumes + --trash-dirs list trash dirs + --trash-dir TRASH_DIRS + specify the trash directory to use + --all-users list trashcans of all the users + +Report bugs to https://github.com/andreafrancia/trash-cli/issues +"""), result.stderr + result.reformatted_help()) + + def tearDown(self): + self.temp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_list/cmd/test_trash_list.py trash-cli-0.23.11.10/tests/test_list/cmd/test_trash_list.py --- trash-cli-0.23.2.13.2/tests/test_list/cmd/test_trash_list.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/cmd/test_trash_list.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,91 @@ +# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy + +from datetime import datetime + +from tests.support.sort_lines import sort_lines +from tests.test_list.cmd.setup import Setup + +from tests.support.asserts import assert_equals_with_unidiff + + +class TestTrashList(Setup): + + def test_should_output_nothing_when_trashcan_is_empty(self): + self.user.run_trash_list() + + assert_equals_with_unidiff('', self.user.output()) + + def test_should_output_deletion_date_and_path(self): + self.user.home_trashdir.add_trashinfo2('/absolute/path', + datetime(2001, 2, 3, 23, 55, 59)) + + self.user.run_trash_list() + + assert_equals_with_unidiff("2001-02-03 23:55:59 /absolute/path\n", + self.user.output()) + + def test_should_output_info_for_multiple_files(self): + self.user.home_trashdir.add_trashinfo2("/file1", + datetime(2000, 1, 1, 0, 0, 1)) + self.user.home_trashdir.add_trashinfo2("/file2", + datetime(2000, 1, 1, 0, 0, 2)) + self.user.home_trashdir.add_trashinfo2("/file3", + datetime(2000, 1, 1, 0, 0, 3)) + + self.user.run_trash_list() + output = self.user.output() + + assert_equals_with_unidiff("2000-01-01 00:00:01 /file1\n" + "2000-01-01 00:00:02 /file2\n" + "2000-01-01 00:00:03 /file3\n", + sort_lines(output)) + + def test_should_output_unknown_dates_with_question_marks(self): + self.user.home_trashdir.add_trashinfo_without_date('without-date') + + self.user.run_trash_list() + + assert self.user.output() == "????-??-?? ??:??:?? /without-date\n" + + def test_should_output_invalid_dates_using_question_marks(self): + self.user.home_trashdir.add_trashinfo_wrong_date('with-invalid-date', + 'Wrong date') + + self.user.run_trash_list() + + assert_equals_with_unidiff("????-??-?? ??:??:?? /with-invalid-date\n", + self.user.output()) + + def test_should_warn_about_empty_trashinfos(self): + self.user.home_trashdir.add_trashinfo_content('empty', '') + + self.user.run_trash_list() + + assert_equals_with_unidiff( + "Parse Error: %(XDG_DATA_HOME)s/Trash/info/empty.trashinfo: " + "Unable to parse Path.\n" % {"XDG_DATA_HOME": self.xdg_data_home}, + self.user.error()) + + def test_should_warn_about_unreadable_trashinfo(self): + self.user.home_trashdir.add_unreadable_trashinfo('unreadable') + + self.user.run_trash_list() + + assert_equals_with_unidiff( + "[Errno 13] Permission denied: " + "'%(XDG_DATA_HOME)s/Trash/info/unreadable.trashinfo'\n" % { + 'XDG_DATA_HOME': self.xdg_data_home + }, + self.user.error()) + + def test_should_warn_about_unexistent_path_entry(self): + self.user.home_trashdir.add_trashinfo_without_path("foo") + + self.user.run_trash_list() + + assert_equals_with_unidiff( + "Parse Error: %(XDG_DATA_HOME)s/Trash/info/foo.trashinfo: " + "Unable to parse Path.\n" % { + 'XDG_DATA_HOME': self.xdg_data_home}, + self.user.error()) + assert_equals_with_unidiff('', self.user.output()) diff -Nru trash-cli-0.23.2.13.2/tests/test_list/cmd/test_version.py trash-cli-0.23.11.10/tests/test_list/cmd/test_version.py --- trash-cli-0.23.2.13.2/tests/test_list/cmd/test_version.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/cmd/test_version.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,14 @@ +# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy + +from tests.support.asserts import assert_equals_with_unidiff +from tests.test_list.cmd.setup import Setup + + +class TestVersion(Setup): + def test_should_output_the_version(self): + self.user.set_version('1.2.3') + + self.user.run_trash_list('--version') + + assert_equals_with_unidiff('trash-list 1.2.3\n', + self.user.output()) diff -Nru trash-cli-0.23.2.13.2/tests/test_list/cmd/test_with_a_top_trash_dir.py trash-cli-0.23.11.10/tests/test_list/cmd/test_with_a_top_trash_dir.py --- trash-cli-0.23.2.13.2/tests/test_list/cmd/test_with_a_top_trash_dir.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/cmd/test_with_a_top_trash_dir.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,91 @@ +# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy + +import os +from datetime import datetime + +from tests.test_list.cmd.setup import Setup + +from tests.support.asserts import assert_equals_with_unidiff +from tests.fake_trash_dir import FakeTrashDir +from tests.support.files import make_sticky_dir, make_unsticky_dir + + +class TestWithATopTrashDir(Setup): + def setUp(self): + super(type(self), self).setUp() + self.top_trashdir1 = FakeTrashDir(self.top_dir / '.Trash/123') + self.user.set_fake_uid(123) + self.user.add_volume(self.top_dir) + + def test_should_list_its_contents_if_parent_is_sticky(self): + make_sticky_dir(self.top_dir / '.Trash') + self.and_contains_a_valid_trashinfo() + + self.user.run_trash_list() + + assert_equals_with_unidiff("2000-01-01 00:00:00 %s/file1\n" % self.top_dir, + self.user.output()) + + def test_and_should_warn_if_parent_is_not_sticky(self): + make_unsticky_dir(self.top_dir / '.Trash') + self.and_dir_exists(self.top_dir / '.Trash/123') + + self.user.run_trash_list() + + assert_equals_with_unidiff( + "TrashDir skipped because parent not sticky: %s/.Trash/123\n" % + self.top_dir, + self.user.error() + ) + + def test_but_it_should_not_warn_when_the_parent_is_unsticky_but_there_is_no_trashdir(self): + make_unsticky_dir(self.top_dir / '.Trash') + self.but_does_not_exists_any(self.top_dir / '.Trash/123') + + self.user.run_trash_list() + + assert_equals_with_unidiff("", self.user.error()) + + def test_should_ignore_trash_from_a_unsticky_topdir(self): + make_unsticky_dir(self.top_dir / '.Trash') + self.and_contains_a_valid_trashinfo() + + self.user.run_trash_list() + + assert_equals_with_unidiff('', self.user.output()) + + def test_it_should_ignore_Trash_is_a_symlink(self): + self.when_is_a_symlink_to_a_dir(self.top_dir / '.Trash') + self.and_contains_a_valid_trashinfo() + + self.user.run_trash_list() + + assert_equals_with_unidiff('', self.user.output()) + + def test_and_should_warn_about_it(self): + self.when_is_a_symlink_to_a_dir(self.top_dir / '.Trash') + self.and_contains_a_valid_trashinfo() + + self.user.run_trash_list() + + assert_equals_with_unidiff( + 'TrashDir skipped because parent not sticky: %s/.Trash/123\n' % + self.top_dir, + self.user.error() + ) + + def but_does_not_exists_any(self, path): + assert not os.path.exists(path) + + def and_dir_exists(self, path): + os.mkdir(path) + assert os.path.isdir(path) + + def and_contains_a_valid_trashinfo(self): + self.top_trashdir1.add_trashinfo2('file1', datetime(2000, 1, 1, 0, 0, 0)) + + def when_is_a_symlink_to_a_dir(self, path): + dest = "%s-dest" % path + os.mkdir(dest) + rel_dest = os.path.basename(dest) + os.symlink(rel_dest, path) diff -Nru trash-cli-0.23.2.13.2/tests/test_list/components/test_deletion_date_extractor.py trash-cli-0.23.11.10/tests/test_list/components/test_deletion_date_extractor.py --- trash-cli-0.23.2.13.2/tests/test_list/components/test_deletion_date_extractor.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/components/test_deletion_date_extractor.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,17 @@ +import datetime +import unittest + +from trashcli.list.extractors import DeletionDateExtractor + + +class TestDeletionDateExtractor(unittest.TestCase): + def setUp(self): + self.extractor = DeletionDateExtractor() + + def test_extract_attribute_default(self): + result = self.extractor.extract_attribute(None, "DeletionDate=") + assert result == '????-??-?? ??:??:??' + + def test_extract_attribute_value(self): + result = self.extractor.extract_attribute(None, "DeletionDate=2001-01-01T10:10:10") + assert result == datetime.datetime(2001, 1, 1, 10, 10, 10) diff -Nru trash-cli-0.23.2.13.2/tests/test_list/components/test_file_size.py trash-cli-0.23.11.10/tests/test_list/components/test_file_size.py --- trash-cli-0.23.2.13.2/tests/test_list/components/test_file_size.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/components/test_file_size.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,19 @@ +import unittest + +from tests.support.files import make_file +from tests.support.my_path import MyPath + +from trashcli import fs + + +class Test_file_size(unittest.TestCase): + def setUp(self): + self.tmp_dir = MyPath.make_temp_dir() + + def test(self): + make_file(self.tmp_dir / 'a-file', '123') + result = fs.file_size(self.tmp_dir / 'a-file') + assert 3 == result + + def tearDown(self): + self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_list/components/test_trash_dirs_selector.py trash-cli-0.23.11.10/tests/test_list/components/test_trash_dirs_selector.py --- trash-cli-0.23.2.13.2/tests/test_list/components/test_trash_dirs_selector.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/components/test_trash_dirs_selector.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,39 @@ +import unittest + +from tests.support.fake_volume_of import volume_of_stub +from trashcli.list.trash_dir_selector import TrashDirsSelector +from trashcli.trash_dirs_scanner import trash_dir_found + + +class MockScanner: + def __init__(self, name): + self.name = name + + def scan_trash_dirs(self, environ, uid): + return [self.name, environ, uid] + + +class TestTrashDirsSelector(unittest.TestCase): + def setUp(self): + volumes = volume_of_stub(lambda x: "volume_of %s" % x) + self.selector = TrashDirsSelector(MockScanner("user"), + MockScanner("all"), + volumes) + + def test_default(self): + result = list(self.selector.select(False, [], 'environ', 'uid')) + + assert result == ['user', 'environ', 'uid'] + + def test_user_specified(self): + result = list(self.selector.select(False, ['user-specified-dirs'], + 'environ', 'uid')) + + assert result == [(trash_dir_found, ('user-specified-dirs', + 'volume_of user-specified-dirs'))] + + def test_all_user_specified(self): + result = list(self.selector.select(True, ['user-specified-dirs'], + 'environ', 'uid')) + + assert result == ['all', 'environ', 'uid'] diff -Nru trash-cli-0.23.2.13.2/tests/test_list/components/test_trash_list_parser.py trash-cli-0.23.11.10/tests/test_list/components/test_trash_list_parser.py --- trash-cli-0.23.2.13.2/tests/test_list/components/test_trash_list_parser.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/components/test_trash_list_parser.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,49 @@ +import unittest + +import trashcli.list +import trashcli.list.main +import trashcli.list.parser +from trashcli.lib.print_version import PrintVersionArgs + + +class TestTrashListParser(unittest.TestCase): + def setUp(self): + self.parser = trashcli.list.parser.Parser("trash-list") + + def test_version(self): + args = self.parse(['--version']) + + assert PrintVersionArgs == type(args) + + def test_trash_dir_not_specified(self): + args = self.parse([]) + + assert [] == args.trash_dirs + + def test_trash_dir_specified(self): + args = self.parse(['--trash-dir=foo']) + + assert ['foo'] == args.trash_dirs + + def test_size_off(self): + args = self.parse([]) + + assert 'deletion_date' == args.attribute_to_print + + def test_size_on(self): + args = self.parse(['--size']) + + assert 'size' == args.attribute_to_print + + def test_files_off(self): + args = self.parse([]) + + assert False == args.show_files + + def test_files_on(self): + args = self.parse(['--files']) + + assert True == args.show_files + + def parse(self, args): + return self.parser.parse_list_args(args, 'trash-list') diff -Nru trash-cli-0.23.2.13.2/tests/test_list/test_deletion_date_extractor.py trash-cli-0.23.11.10/tests/test_list/test_deletion_date_extractor.py --- trash-cli-0.23.2.13.2/tests/test_list/test_deletion_date_extractor.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/test_deletion_date_extractor.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -import datetime -import unittest - -from trashcli.list import DeletionDateExtractor - - -class TestDeletionDateExtractor(unittest.TestCase): - def setUp(self): - self.extractor = DeletionDateExtractor() - - def test_extract_attribute_default(self): - result = self.extractor.extract_attribute(None, "DeletionDate=") - assert result == '????-??-?? ??:??:??' - - def test_extract_attribute_value(self): - result = self.extractor.extract_attribute(None, "DeletionDate=2001-01-01T10:10:10") - assert result == datetime.datetime(2001, 1, 1, 10, 10, 10) diff -Nru trash-cli-0.23.2.13.2/tests/test_list/test_end_to_end_list.py trash-cli-0.23.11.10/tests/test_list/test_end_to_end_list.py --- trash-cli-0.23.2.13.2/tests/test_list/test_end_to_end_list.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/test_end_to_end_list.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,87 +0,0 @@ -import datetime -import unittest - -import pytest - -from .. import run_command -from ..fake_trash_dir import FakeTrashDir -from ..run_command import normalize_options -from ..support.my_path import MyPath - - -@pytest.mark.slow -class TestEndToEndList(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - self.trash_dir = self.tmp_dir / 'trash-dir' - self.fake_trash_dir = FakeTrashDir(self.trash_dir) - - def test_list(self): - self.fake_trash_dir.add_trashinfo2("/file1", - datetime.datetime(2000, 1, 1, 0, 0, - 1)) - self.fake_trash_dir.add_trashinfo2("/file2", - datetime.datetime(2000, 1, 1, 0, 0, - 1)) - - result = run_command.run_command(self.tmp_dir, "trash-list", - ['--trash-dir', self.trash_dir]) - - assert [ - '2000-01-01 00:00:01 /file1', - '2000-01-01 00:00:01 /file2', - ] == sorted(result.stdout.splitlines()) - - def test_list_trash_dirs(self): - result = run_command.run_command( - self.tmp_dir, "trash-list", - ['--trash-dirs', '--trash-dir=/home/user/.local/share/Trash']) - assert (result.stderr, - sorted(result.stdout.splitlines()), result.exit_code) == ( - '', [ - '/home/user/.local/share/Trash' - ], 0) - - def test_list_with_paths(self): - self.fake_trash_dir.add_trashinfo3("base1", "/file1", - datetime.datetime(2000, 1, 1, 0, 0, - 1)) - self.fake_trash_dir.add_trashinfo3("base2", "/file2", - datetime.datetime(2000, 1, 1, 0, 0, - 1)) - - result = run_command.run_command(self.tmp_dir, "trash-list", - ['--trash-dir', self.trash_dir, - '--files']) - - assert ('', [ - '2000-01-01 00:00:01 /file1 -> %s/files/base1' % self.trash_dir, - '2000-01-01 00:00:01 /file2 -> %s/files/base2' % self.trash_dir, - ]) == (result.stderr, sorted(result.stdout.splitlines())) - - def test_help(self): - result = run_command.run_command(self.tmp_dir, "trash-list", ['--help']) - - self.assertEqual("""\ -usage: trash-list [-h] [--print-completion {bash,zsh,tcsh}] [--version] - [--volumes] [--trash-dirs] [--trash-dir TRASH_DIRS] - [--all-users] - -List trashed files - -options: - -h, --help show this help message and exit - --print-completion {bash,zsh,tcsh} - print shell completion script - --version show program's version number and exit - --volumes list volumes - --trash-dirs list trash dirs - --trash-dir TRASH_DIRS - specify the trash directory to use - --all-users list trashcans of all the users - -Report bugs to https://github.com/andreafrancia/trash-cli/issues -""", normalize_options(result.stdout)) - - def tearDown(self): - self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_list/test_file_size.py trash-cli-0.23.11.10/tests/test_list/test_file_size.py --- trash-cli-0.23.2.13.2/tests/test_list/test_file_size.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/test_file_size.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -import unittest - -from tests.support.files import make_file -from tests.support.my_path import MyPath - -from trashcli import fs - - -class Test_file_size(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - - def test(self): - make_file(self.tmp_dir / 'a-file', '123') - result = fs.file_size(self.tmp_dir / 'a-file') - assert 3 == result - - def tearDown(self): - self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_list/test_trash_dirs_selector.py trash-cli-0.23.11.10/tests/test_list/test_trash_dirs_selector.py --- trash-cli-0.23.2.13.2/tests/test_list/test_trash_dirs_selector.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/test_trash_dirs_selector.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,40 +0,0 @@ -import unittest - -from tests.support.volumes_mock import volumes_mock - -from trashcli.list import TrashDirsSelector -from trashcli.trash_dirs_scanner import trash_dir_found - - -class MockScanner: - def __init__(self, name): - self.name = name - - def scan_trash_dirs(self, environ, uid): - return [self.name, environ, uid] - - -class TestTrashDirsSelector(unittest.TestCase): - def setUp(self): - volumes = volumes_mock(lambda x: "volume_of %s" % x) - self.selector = TrashDirsSelector(MockScanner("user"), - MockScanner("all"), - volumes) - - def test_default(self): - result = list(self.selector.select(False, [], 'environ', 'uid')) - - assert result == ['user', 'environ', 'uid'] - - def test_user_specified(self): - result = list(self.selector.select(False, ['user-specified-dirs'], - 'environ', 'uid')) - - assert result == [(trash_dir_found, ('user-specified-dirs', - 'volume_of user-specified-dirs'))] - - def test_all_user_specified(self): - result = list(self.selector.select(True, ['user-specified-dirs'], - 'environ', 'uid')) - - assert result == ['all', 'environ', 'uid'] diff -Nru trash-cli-0.23.2.13.2/tests/test_list/test_trash_list_parser.py trash-cli-0.23.11.10/tests/test_list/test_trash_list_parser.py --- trash-cli-0.23.2.13.2/tests/test_list/test_trash_list_parser.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list/test_trash_list_parser.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,63 +0,0 @@ -import unittest - -from six import StringIO - -import trashcli.list -from trashcli.list import Action -from trashcli.trash import PrintHelp - - -class TestTrashListParser(unittest.TestCase): - def setUp(self): - self.parser = trashcli.list.Parser("trash-list") - - def test_version(self): - parsed = self.parser.parse_list_args(['--version']) - - assert Action.print_version == parsed.action - - def test_trash_dir_not_specified(self): - parsed = self.parser.parse_list_args([]) - - assert [] == parsed.trash_dirs - - def test_trash_dir_specified(self): - parsed = self.parser.parse_list_args(['--trash-dir=foo']) - - assert ['foo'] == parsed.trash_dirs - - def test_size_off(self): - parsed = self.parser.parse_list_args([]) - - assert 'deletion_date' == parsed.attribute_to_print - - def test_size_on(self): - parsed = self.parser.parse_list_args(['--size']) - - assert 'size' == parsed.attribute_to_print - - def test_files_off(self): - parsed = self.parser.parse_list_args([]) - - assert False == parsed.show_files - - def test_files_on(self): - parsed = self.parser.parse_list_args(['--files']) - - assert True == parsed.show_files - - -class TestPrintHelp(unittest.TestCase): - def test(self): - out = StringIO() - help_printer = PrintHelp(trashcli.list.description, out) - help_printer.my_print_help('trash-list') - assert out.getvalue() == ('Usage: trash-list [OPTIONS...]\n' - '\n' - 'List trashed files\n' - '\n' - 'Options:\n' - " --version show program's version number and exit\n" - ' -h, --help show this help message and exit\n' - '\n' - 'Report bugs to https://github.com/andreafrancia/trash-cli/issues\n') diff -Nru trash-cli-0.23.2.13.2/tests/test_list_slow.py trash-cli-0.23.11.10/tests/test_list_slow.py --- trash-cli-0.23.2.13.2/tests/test_list_slow.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_list_slow.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -import unittest - -import pytest - -from trashcli.fs import FileSystemReader -from trashcli.rm import ListTrashinfos -from .fake_trash_dir import FakeTrashDir -from .support.my_path import MyPath - - -@pytest.mark.slow -class TestListTrashinfos(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - self.trash_dir = self.tmp_dir / 'Trash' - self.fake_trash_dir = FakeTrashDir(self.trash_dir) - self.listing = ListTrashinfos(FileSystemReader()) - - def test_absolute_path(self): - self.fake_trash_dir.add_trashinfo_basename_path('a', '/foo') - - result = list(self.listing.list_from_volume_trashdir(self.trash_dir, - '/volume/')) - - assert result == [('trashed_file', - ('/foo', '%s/info/a.trashinfo' % self.trash_dir))] - - def test_relative_path(self): - self.fake_trash_dir.add_trashinfo_basename_path('a', 'foo') - - result = list(self.listing.list_from_volume_trashdir(self.trash_dir, - '/volume/')) - - assert result == [('trashed_file', - ('/volume/foo', '%s/info/a.trashinfo' % self.trash_dir))] - - def tearDown(self): - self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_parsing_trashinfo_contents.py trash-cli-0.23.11.10/tests/test_parsing_trashinfo_contents.py --- trash-cli-0.23.2.13.2/tests/test_parsing_trashinfo_contents.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_parsing_trashinfo_contents.py 2023-11-10 07:15:04.000000000 +0000 @@ -3,31 +3,31 @@ from datetime import datetime from mock import MagicMock -from trashcli.trash import ( - ParseError, - ParseTrashInfo, - maybe_parse_deletion_date, - parse_deletion_date, - parse_original_location, - parse_path, - unknown_date, -) + +from trashcli.parse_trashinfo.parse_path import parse_path +from trashcli.parse_trashinfo.parse_trashinfo import ParseTrashInfo +from trashcli.parse_trashinfo.maybe_parse_deletion_date import \ + maybe_parse_deletion_date, unknown_date +from trashcli.parse_trashinfo.parse_original_location import \ + parse_original_location +from trashcli.parse_trashinfo.parser_error import ParseError +from trashcli.parse_trashinfo.parse_deletion_date import parse_deletion_date class TestParseTrashInfo(unittest.TestCase): def test_it_should_parse_date(self): out = MagicMock() - parser = ParseTrashInfo(on_deletion_date = out) + parser = ParseTrashInfo(on_deletion_date=out) parser.parse_trashinfo('[Trash Info]\n' 'Path=foo\n' 'DeletionDate=1970-01-01T00:00:00\n') - out.assert_called_with(datetime(1970,1,1,0,0,0)) + out.assert_called_with(datetime(1970, 1, 1, 0, 0, 0)) def test_it_should_parse_path(self): out = MagicMock() - parser = ParseTrashInfo(on_path = out) + parser = ParseTrashInfo(on_path=out) parser.parse_trashinfo('[Trash Info]\n' 'Path=foo\n' @@ -39,11 +39,11 @@ class TestParseDeletionDate(unittest.TestCase): def test1(self): assert parse_deletion_date('DeletionDate=2000-12-31T23:59:58') == \ - datetime(2000,12,31,23,59,58) + datetime(2000, 12, 31, 23, 59, 58) def test2(self): assert parse_deletion_date('DeletionDate=2000-12-31T23:59:58\n') == \ - datetime(2000,12,31,23,59,58) + datetime(2000, 12, 31, 23, 59, 58) def test3(self): assert parse_deletion_date( @@ -59,23 +59,26 @@ class Test_maybe_parse_deletion_date(unittest.TestCase): def test_on_trashinfo_without_date_parse_to_unknown_date(self): assert (unknown_date == - maybe_parse_deletion_date(a_trashinfo_without_deletion_date())) + maybe_parse_deletion_date(a_trashinfo_without_deletion_date())) def test_on_trashinfo_with_date_parse_to_date(self): from datetime import datetime - example_date_as_string='2001-01-01T00:00:00' - same_date_as_datetime=datetime(2001,1,1) + example_date_as_string = '2001-01-01T00:00:00' + same_date_as_datetime = datetime(2001, 1, 1) assert (same_date_as_datetime == - maybe_parse_deletion_date(make_trashinfo(example_date_as_string))) + maybe_parse_deletion_date( + make_trashinfo(example_date_as_string))) def test_on_trashinfo_with_invalid_date_parse_to_unknown_date(self): - invalid_date='A long time ago' + invalid_date = 'A long time ago' assert (unknown_date == - maybe_parse_deletion_date(make_trashinfo(invalid_date))) + maybe_parse_deletion_date(make_trashinfo(invalid_date))) + def test_how_to_parse_original_path(): - assert 'foo.txt' == parse_path('Path=foo.txt') - assert '/path/to/be/escaped' == parse_path('Path=%2Fpath%2Fto%2Fbe%2Fescaped') + assert 'foo.txt' == parse_path('Path=foo.txt') + assert '/path/to/be/escaped' == parse_path( + 'Path=%2Fpath%2Fto%2Fbe%2Fescaped') class TestTrashInfoParser(unittest.TestCase): diff -Nru trash-cli-0.23.2.13.2/tests/test_partitions.py trash-cli-0.23.11.10/tests/test_partitions.py --- trash-cli-0.23.2.13.2/tests/test_partitions.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_partitions.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,6 @@ import unittest -from trashcli.list_mount_points import Partitions +from trashcli.fstab.mount_points_listing import Partitions class MockPartition: diff -Nru trash-cli-0.23.2.13.2/tests/test_put/gate_impl/test_gate_check_result.py trash-cli-0.23.11.10/tests/test_put/gate_impl/test_gate_check_result.py --- trash-cli-0.23.2.13.2/tests/test_put/gate_impl/test_gate_check_result.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/gate_impl/test_gate_check_result.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -import unittest - -from trashcli.put.gate_impl import GateCheckResult - - -class TestGateCheckResult(unittest.TestCase): - - def test_equality(self): - a = GateCheckResult(True, 'msg') - b = GateCheckResult(True, 'msg') - - assert a == b - - def test_not_equals_by_ok(self): - a = GateCheckResult(True, 'msg') - b = GateCheckResult(False, 'msg') - - assert a != b - - def test_not_equals_by_string(self): - a = GateCheckResult(True, 'msg1') - b = GateCheckResult(True, 'msg2') - - assert a != b diff -Nru trash-cli-0.23.2.13.2/tests/test_put/real_fs/test_fake_fs_walk_no_follow.py trash-cli-0.23.11.10/tests/test_put/real_fs/test_fake_fs_walk_no_follow.py --- trash-cli-0.23.2.13.2/tests/test_put/real_fs/test_fake_fs_walk_no_follow.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/real_fs/test_fake_fs_walk_no_follow.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,16 @@ +from tests.test_put.support.fake_fs.fake_fs import FakeFs + + +class TestWalkNoFollow: + def setup_method(self): + self.fs = FakeFs() + + def test(self): + self.fs.make_file("pippo") + self.fs.makedirs("/a/b/c/d", 0o700) + + assert "\n".join(self.fs.list_all()) == '/a\n' \ + '/pippo\n' \ + '/a/b\n' \ + '/a/b/c\n' \ + '/a/b/c/d' diff -Nru trash-cli-0.23.2.13.2/tests/test_put/support/dummy_clock.py trash-cli-0.23.11.10/tests/test_put/support/dummy_clock.py --- trash-cli-0.23.2.13.2/tests/test_put/support/dummy_clock.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/support/dummy_clock.py 2023-11-10 07:15:04.000000000 +0000 @@ -3,7 +3,7 @@ from trashcli.put.clock import PutClock -class DummyClock(PutClock): +class FixedClock(PutClock): def __init__(self, now_value=None): self.now_value = now_value @@ -12,3 +12,10 @@ def now(self): # type: () -> datetime.datetime return self.now_value + + @staticmethod + def fixet_at_jan_1st_2024(): + return FixedClock(now_value=jan_1st_2024()) + +def jan_1st_2024(): + return datetime.datetime(2014, 1, 1, 0, 0, 0) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/support/fake_fs/failing_fake_fs.py trash-cli-0.23.11.10/tests/test_put/support/fake_fs/failing_fake_fs.py --- trash-cli-0.23.2.13.2/tests/test_put/support/fake_fs/failing_fake_fs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/support/fake_fs/failing_fake_fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,54 @@ +import os.path +from tests.test_put.support.fake_fs.fake_fs import FakeFs + + +class FailingOnAtomicWriteFakeFs(FakeFs): + def __init__(self): + super(FailingOnAtomicWriteFakeFs, self).__init__() + self._atomic_write_can_fail = False + self._atomic_write_failure_stop = None + + def fail_atomic_create_unless(self, basename): + self._atomic_write_can_fail = True + self._atomic_write_failure_stop = basename + + def atomic_write(self, + path, + content): + if self._atomic_write_is_supposed_to_fail(path): + raise OSError("atomic_write failed") + + return super(FailingOnAtomicWriteFakeFs, self).atomic_write(path, + content) + + def _atomic_write_is_supposed_to_fail(self, + path, # type: str + ): # type: (...) -> bool + result = (self._atomic_write_can_fail and + os.path.basename(path) != self._atomic_write_failure_stop) + return result + + +class FailOnMoveFakeFs(FakeFs): + def __init__(self): + super(FailOnMoveFakeFs, self).__init__() + self._fail_move_on_path = None + + def move(self, src, dest): + if src == self._fail_move_on_path: + raise OSError("move failed") + return super(FailOnMoveFakeFs, self).move(src, dest) + + def fail_move_on(self, path): + self._fail_move_on_path = path + + +class FailingFakeFs(FailingOnAtomicWriteFakeFs, + FailOnMoveFakeFs): + def __init__(self): + super(FailingFakeFs, self).__init__() + + def assert_does_not_exist(self, path): + if self.exists(path): + raise AssertionError( + "expected path to not exists but it does: %s" % path) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/support/fake_fs/fake_fs.py trash-cli-0.23.11.10/tests/test_put/support/fake_fs/fake_fs.py --- trash-cli-0.23.2.13.2/tests/test_put/support/fake_fs/fake_fs.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/support/fake_fs/fake_fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,24 +1,34 @@ import os +from typing import Union + from tests.test_put.support.fake_fs.directory import Directory, \ make_inode_for_dir -from tests.test_put.support.fake_fs.inode import SymLink from tests.test_put.support.fake_fs.file import File +from tests.test_put.support.fake_fs.inode import SymLink from tests.test_put.support.format_mode import format_mode from tests.test_put.support.my_file_not_found_error import MyFileNotFoundError +from trashcli.fs import PathExists from trashcli.put.fs.fs import Fs -class FakeFs(Fs): +class FakeFs(Fs, PathExists): def __init__(self, cwd='/'): directory = Directory('/') make_inode_for_dir(directory, 0o755) self.root = directory self.cwd = cwd + def touch(self, path): + if not self.exists(path): + self.make_file(path, '') + def listdir(self, path): return self.ls_aa(path) + def ls_existing(self, paths): + return [p for p in paths if self.exists(p)] + def ls_aa(self, path): all_entries = self.ls_a(path) all_entries.remove(".") @@ -34,7 +44,7 @@ dir = self.find_dir_or_file(dirname) dir.add_dir(basename, 0o755, path) - def find_dir_or_file(self, path): # type: (str) -> Directory or File + def find_dir_or_file(self, path): # type: (str) -> Union[Directory,File] path = os.path.join(self.cwd, path) if path == '/': return self.root @@ -44,13 +54,18 @@ cur_dir = cur_dir.get_file(component) except KeyError: raise MyFileNotFoundError( - "no such file or directory: %s" % path) + "no such file or directory: %s\n%s" % ( + path, + "\n".join(self.list_all()), + )) return cur_dir def components_for(self, path): return path.split('/')[1:] def atomic_write(self, path, content): + if self.exists(path): + raise OSError("already exists: %s" % path) self.make_file(path, content) def read(self, path): @@ -195,3 +210,11 @@ def lexists(self, path): # TODO: consider links return self.exists(path) + + def list_all(self): + result = self.walk_no_follow("/") + for top, dirs, non_dirs in result: + for d in dirs: + yield os.path.join(top, d) + for f in non_dirs: + yield os.path.join(top, f) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/support/fake_fs_with_realpath.py trash-cli-0.23.11.10/tests/test_put/support/fake_fs_with_realpath.py --- trash-cli-0.23.2.13.2/tests/test_put/support/fake_fs_with_realpath.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/support/fake_fs_with_realpath.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -class FakeFsWithRealpath: - @staticmethod - def realpath(path): - return path diff -Nru trash-cli-0.23.2.13.2/tests/test_put/support/fake_random.py trash-cli-0.23.11.10/tests/test_put/support/fake_random.py --- trash-cli-0.23.2.13.2/tests/test_put/support/fake_random.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/support/fake_random.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,16 @@ +from typing import List + +from trashcli.put.core.int_generator import IntGenerator + + +class FakeRandomInt(IntGenerator): + def __init__(self, + values, # type: List[int] + ): + self.values = values + + def new_int(self, _a, _b): + return self.values.pop(0) + + def set_reply(self, value): + self.values = [value] diff -Nru trash-cli-0.23.2.13.2/tests/test_put/support/my_file_not_found_error.py trash-cli-0.23.11.10/tests/test_put/support/my_file_not_found_error.py --- trash-cli-0.23.2.13.2/tests/test_put/support/my_file_not_found_error.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/support/my_file_not_found_error.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,6 @@ try: FileNotFoundError except NameError: - FileNotFoundError = IOError + FileNotFoundError = OSError MyFileNotFoundError = FileNotFoundError diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_create_trashinfo_basename.py trash-cli-0.23.11.10/tests/test_put/test_create_trashinfo_basename.py --- trash-cli-0.23.2.13.2/tests/test_put/test_create_trashinfo_basename.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_create_trashinfo_basename.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,9 +1,8 @@ -import unittest +from trashcli.put.janitor_tools.info_file_persister import \ + create_trashinfo_basename -from trashcli.put.info_dir import create_trashinfo_basename - -class Test_create_trashinfo_basename(unittest.TestCase): +class TestCreateTrashinfoBasename: def test_when_file_name_is_not_too_long(self): assert 'basename_1.trashinfo' == create_trashinfo_basename('basename', '_1', diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_end_to_end_put.py trash-cli-0.23.11.10/tests/test_put/test_end_to_end_put.py --- trash-cli-0.23.2.13.2/tests/test_put/test_end_to_end_put.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_end_to_end_put.py 2023-11-10 07:15:04.000000000 +0000 @@ -2,14 +2,13 @@ import unittest from textwrap import dedent +import pytest + from tests import run_command from tests.run_command import first_line_of, last_line_of +from trashcli.lib.exit_codes import EX_IOERR from ..support.my_path import MyPath -import pytest - -from ..run_command import normalize_options - @pytest.mark.slow class TestEndToEndPut(unittest.TestCase): @@ -39,7 +38,7 @@ def test_on_help(self): result = run_command.run_command(self.tmp_dir, "trash-put", ['--help']) - assert [normalize_options(result.stdout), + assert [result.reformatted_help(), result.exit_code] == \ [dedent('''\ usage: trash-put [OPTION]... FILE... @@ -75,13 +74,13 @@ result = run_command.run_command(self.tmp_dir, "trash-put", ['.']) assert [result.stderr, result.exit_code] == \ - ["trash-put: cannot trash directory '.'\n", 0] + ["trash-put: cannot trash directory '.'\n", EX_IOERR] def test_it_should_skip_dotdot_entry(self): result = run_command.run_command(self.tmp_dir, "trash-put", ['..']) assert [result.stderr, result.exit_code] == \ - ["trash-put: cannot trash directory '..'\n", 0] + ["trash-put: cannot trash directory '..'\n", EX_IOERR] def test_it_should_print_usage_on_no_argument(self): result = run_command.run_command(self.tmp_dir, "trash-put", []) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_gate.py trash-cli-0.23.11.10/tests/test_put/test_gate.py --- trash-cli-0.23.2.13.2/tests/test_put/test_gate.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_gate.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,8 +1,9 @@ -from trashcli.put.gate import SameVolumeGate +from trashcli.put.gate import Gate class TestGate: def test_gate(self): - a = SameVolumeGate - assert repr(a) == 'SameVolumeGate' - assert str(a) == 'SameVolumeGate' + assert repr(Gate.SameVolume) == 'Gate.SameVolume' + assert str(Gate.SameVolume) == 'Gate.SameVolume' + assert repr(Gate.HomeFallback) == 'Gate.HomeFallback' + assert str(Gate.HomeFallback) == 'Gate.HomeFallback' diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_home_fallback_gate_impl.py trash-cli-0.23.11.10/tests/test_put/test_home_fallback_gate_impl.py --- trash-cli-0.23.2.13.2/tests/test_put/test_home_fallback_gate_impl.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_home_fallback_gate_impl.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -import os -import unittest -from pprint import pprint - -from .support.fake_fs.fake_fs import FakeFs -from trashcli.put.candidate import Candidate -from trashcli.put.gate import HomeFallbackGate -from trashcli.put.gate_impl import HomeFallbackGateImpl, GateCheckResult -from trashcli.put.path_maker import AbsolutePaths -from trashcli.put.security_check import NoCheck -from trashcli.put.trashee import Trashee - - -class TestHomeFallbackGateImpl(unittest.TestCase): - def setUp(self): - self.fake_fs = FakeFs() - self.gate_impl = HomeFallbackGateImpl(self.fake_fs) - - def test_not_enabled(self): - result = self.gate_impl.can_trash_in(make_trashee(), - make_candidate('/xdf/Trash'), - {}) - assert [result] == [ - GateCheckResult.make_error('trash dir not enabled: /xdf/Trash')] - - def test_enabled(self): - result = self.gate_impl.can_trash_in(make_trashee(), - make_candidate('/xdf/Trash'), - { - "TRASH_ENABLE_HOME_FALLBACK": "1" - }) - assert [result] == [ - GateCheckResult.make_ok()] - - # def test(self): - # result = os.statvfs('/Users/andrea/trash-cli') - # print("") - # pprint(result.f_bavail / 1024 / 1024) - # pprint(result.f_bfree / 1024 / 1024) - # # pprint(psutil.disk_usage('/')) - - -def make_candidate(path): - return Candidate(path, '/disk2', AbsolutePaths, NoCheck, - HomeFallbackGate) - - -def make_trashee(): - return Trashee('/disk1/foo', "/disk1") diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_original_location.py trash-cli-0.23.11.10/tests/test_put/test_original_location.py --- trash-cli-0.23.2.13.2/tests/test_put/test_original_location.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_original_location.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,17 +1,20 @@ +import os import unittest -from parameterized import parameterized +from parameterized import parameterized # type: ignore -from tests.test_put.support.fake_fs_with_realpath import FakeFsWithRealpath -from trashcli.put.fs.parent_realpath import ParentRealpath +from tests.test_put.support.fake_fs.fake_fs import FakeFs +from trashcli.put.core.path_maker_type import PathMakerType from trashcli.put.original_location import OriginalLocation -from trashcli.put.path_maker import PathMaker, AbsolutePaths, RelativePaths + +AbsolutePaths = PathMakerType.AbsolutePaths +RelativePaths = PathMakerType.RelativePaths + class TestOriginalLocation(unittest.TestCase): def setUp(self): - self.original_location = OriginalLocation( - ParentRealpath(FakeFsWithRealpath()), PathMaker()) + self.original_location = OriginalLocation(FakeFsWithRealpath()) @parameterized.expand([ ('/volume', '/file', AbsolutePaths, '/file',), @@ -32,3 +35,8 @@ result = self.original_location.for_file(file_to_be_trashed, path_type, volume) assert expected_result == result + + +class FakeFsWithRealpath(FakeFs): + def parent_realpath2(self, path): + return os.path.dirname(path) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_parent_path.py trash-cli-0.23.11.10/tests/test_put/test_parent_path.py --- trash-cli-0.23.2.13.2/tests/test_put/test_parent_path.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_parent_path.py 2023-11-10 07:15:04.000000000 +0000 @@ -3,14 +3,15 @@ import pytest -from trashcli.put.fs.parent_realpath import ParentRealpath +from trashcli.put.fs.parent_realpath import ParentRealpathFs from trashcli.put.fs.real_fs import RealFs from ..support.files import make_empty_file, require_empty_dir from ..support.my_path import MyPath def parent_path(path): - return ParentRealpath(RealFs()).parent_realpath(path) + return ParentRealpathFs(RealFs()).parent_realpath(path) + @pytest.mark.slow class Test_parent_path(unittest.TestCase): @@ -21,7 +22,8 @@ require_empty_dir(self.tmp_dir / 'other_dir/dir') os.symlink(self.tmp_dir / 'other_dir/dir', self.tmp_dir / 'dir') make_empty_file(self.tmp_dir / 'dir/foo') - assert (self.tmp_dir / 'other_dir/dir' == parent_path(self.tmp_dir / 'dir/foo')) + assert (self.tmp_dir / 'other_dir/dir' == parent_path( + self.tmp_dir / 'dir/foo')) def test2(self): require_empty_dir(self.tmp_dir / 'test-disk/dir') @@ -43,7 +45,7 @@ os.symlink('../bar/zap', self.tmp_dir / 'foo/zap') make_empty_file(self.tmp_dir / 'bar/zap') assert parent_path(self.tmp_dir / 'foo/zap') == \ - os.path.join(self.tmp_dir,'foo') + os.path.join(self.tmp_dir, 'foo') def tearDown(self): self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_persist_trash_info.py trash-cli-0.23.11.10/tests/test_put/test_persist_trash_info.py --- trash-cli-0.23.2.13.2/tests/test_put/test_persist_trash_info.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_persist_trash_info.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -# Copyright (C) 2008-2021 Andrea Francia Trivolzio(PV) Italy - -import unittest - -import pytest -from mock import Mock -from typing import cast - -from trashcli.fs import read_file -from trashcli.put.dir_maker import DirMaker -from trashcli.put.info_dir import InfoDir -from trashcli.put.my_logger import LogData -from trashcli.put.fs.real_fs import RealFs -from ..support.my_path import MyPath - - -@pytest.mark.slow -class Test_persist_trash_info(unittest.TestCase): - def setUp(self): - self.path = MyPath.make_temp_dir() - self.fs = RealFs() - self.logger = Mock() - self.suffix = Mock() - self.suffix.suffix_for_index.side_effect = lambda i: '.suffix-%s' % i - self.info_dir = InfoDir(self.fs, self.logger, self.suffix) - - def test_persist_trash_info_first_time(self): - trash_info_file = self.info_dir.persist_trash_info( - 'dummy-path', b'content', cast(LogData,'log_data'), self.path) - - assert self.path / 'dummy-path.suffix-0.trashinfo' == trash_info_file - assert 'content' == read_file(trash_info_file) - - def test_persist_trash_info_first_100_times(self): - self.test_persist_trash_info_first_time() - - trash_info_file = self.info_dir.persist_trash_info( - 'dummy-path', b'content', cast(LogData,'log_data'), self.path) - - assert self.path / 'dummy-path.suffix-1.trashinfo' == trash_info_file - assert 'content' == read_file(trash_info_file) - - def tearDown(self): - self.path.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_persist_trashinfo.py trash-cli-0.23.11.10/tests/test_put/test_persist_trashinfo.py --- trash-cli-0.23.2.13.2/tests/test_put/test_persist_trashinfo.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_persist_trashinfo.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,49 @@ +# Copyright (C) 2008-2021 Andrea Francia Trivolzio(PV) Italy + +import unittest + +import pytest +from six import StringIO + +from trashcli.fs import read_file +from trashcli.put.fs.real_fs import RealFs +from trashcli.put.janitor_tools.info_file_persister import TrashinfoData, \ + InfoFilePersister +from trashcli.put.my_logger import LogData, MyLogger +from trashcli.put.suffix import Suffix +from .support.fake_random import FakeRandomInt +from ..support.my_path import MyPath + + +@pytest.mark.slow +class TestPersistTrashInfo(unittest.TestCase): + def setUp(self): + self.path = MyPath.make_temp_dir() + self.fs = RealFs() + self.stderr = StringIO() + self.logger = MyLogger(self.stderr) + self.suffix = Suffix(FakeRandomInt([0,1])) + self.info_dir = InfoFilePersister(self.fs, self.logger, self.suffix) + + def test_persist_trash_info_first_time(self): + trash_info_file = self._persist_trash_info('dummy-path', b'content') + + assert self.path / 'dummy-path.trashinfo' == trash_info_file + assert 'content' == read_file(trash_info_file) + + def test_persist_trash_info_first_100_times(self): + self.test_persist_trash_info_first_time() + + trash_info_file = self._persist_trash_info('dummy-path', + b'content') + + assert self.path / 'dummy-path_1.trashinfo' == trash_info_file + assert 'content' == read_file(trash_info_file) + + def tearDown(self): + self.path.clean_up() + + def _persist_trash_info(self, basename, content): + log_data = LogData('trash-cli', 2) + data = TrashinfoData(basename, content, self.path) + return self.info_dir.create_trashinfo_file(data, log_data).trashinfo_path diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_put.py trash-cli-0.23.11.10/tests/test_put/test_put.py --- trash-cli-0.23.2.13.2/tests/test_put/test_put.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_put.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,70 +1,182 @@ -import datetime import os -import unittest +from typing import List +from typing import NamedTuple +from typing import Optional import flexmock from six import StringIO -from tests.test_put.support.dummy_clock import DummyClock -from tests.test_put.support.fake_fs.fake_fs import FakeFs -from trashcli.fstab import Volumes, FakeIsMount +from tests.support.fake_is_mount import FakeIsMount +from tests.test_put.support.dummy_clock import FixedClock +from tests.test_put.support.dummy_clock import jan_1st_2024 +from tests.test_put.support.fake_fs.failing_fake_fs import FailingFakeFs +from tests.test_put.support.fake_random import FakeRandomInt +from trashcli.fstab.volume_of import VolumeOfImpl +from trashcli.lib.environ import Environ +from trashcli.lib.exit_codes import EX_IOERR +from trashcli.lib.exit_codes import EX_OK +from trashcli.lib.my_input import HardCodedInput from trashcli.put.main import make_cmd -from trashcli.trash import EX_IOERR, EX_OK +from trashcli.put.parser import ensure_int -class TestPut(unittest.TestCase): - def setUp(self): - clock = DummyClock(now_value=datetime.datetime(2014, 1, 1, 0, 0, 0)) - self.fs = FakeFs() - my_input = lambda: "y" - randint = lambda: 44 +class TestPut: + def setup_method(self): + self.fs = FailingFakeFs() + self.user_input = HardCodedInput('y') + self.randint = FakeRandomInt([]) self.is_mount = FakeIsMount(['/']) - volumes = Volumes(self.is_mount, os.path.normpath) + self.volumes = VolumeOfImpl(self.is_mount, os.path.normpath) self.stderr = StringIO() - self.cmd = make_cmd(clock=clock, + self.clock = FixedClock(jan_1st_2024()) + self.cmd = make_cmd(clock=self.clock, fs=self.fs, - my_input=my_input, - randint=randint, + user_input=self.user_input, + randint=self.randint, stderr=self.stderr, - volumes=volumes) + volumes=self.volumes) + + def test_when_needs_a_different_suffix(self): + self.fs.touch("/foo") + self.fs.fail_atomic_create_unless("foo_1.trashinfo") + + self.run_cmd(['trash-put', '/foo']) + + assert self.fs.ls_aa('/.Trash-123/files') == ['foo_1'] + + def test_when_needs_a_random_suffix(self): + self.fs.touch("/foo") + self.fs.fail_atomic_create_unless("foo_123.trashinfo") + self.randint.set_reply(123) + + self.run_cmd(['trash-put', '/foo']) + + assert self.fs.ls_aa('/.Trash-123/files') == ['foo_123'] + + def test_when_a_trashinfo_file_already_exists(self): + def touch_and_trash(path): + self.fs.touch(path) + self.run_cmd(['trash-put', path]) + + touch_and_trash("/foo") + touch_and_trash("/foo") + touch_and_trash("/foo") + + assert self.fs.ls_aa('/.Trash-123/info') == [ + 'foo.trashinfo', + 'foo_1.trashinfo', + 'foo_2.trashinfo' + ] + + def test_when_moving_file_in_trash_dir_fails(self): + self.fs.touch("foo") + self.fs.fail_move_on("/foo") + + result = self.run_cmd(['trash-put', '-v', '/foo']) + + assert result.all() == [EX_IOERR, [ + "trash-put: cannot trash regular empty file '/foo' (from volume '/')", + 'trash-put: `- failed to trash /foo in /.Trash/123, because trash dir cannot be created because its parent does not exists, trash-dir: /.Trash/123, parent: /.Trash', + 'trash-put: `- failed to trash /foo in /.Trash-123, because failed to move /foo in /.Trash-123/files: move failed']] + + def test_should_not_trash_dot_entry(self): + result = self.run_cmd(['trash-put', '.']) + + assert result.all() == [ + EX_IOERR, + ["trash-put: cannot trash directory '.'"]] + + def test_should_not_trash_dot_dot_entry(self): + result = self.run_cmd(['trash-put', '..']) + + assert result.all() == [ + EX_IOERR, + ["trash-put: cannot trash directory '..'"]] + + def test_user_reply_no(self): + self.fs.touch("foo") + self.user_input.set_reply('n') + + result = self.run_cmd(['trash-put', '-i', 'foo']) + + assert result.all() + [self.user_input.last_prompt(), + self.fs.ls_existing(["foo"])] == [ + EX_OK, [], "trash-put: trash regular empty file 'foo'? ", + ['foo'], + ] + + def test_user_reply_yes(self): + self.fs.touch("foo") + self.user_input.set_reply('y') + + result = self.run_cmd(['trash-put', '-i', 'foo']) + + assert result.all() + [self.user_input.last_prompt(), + self.fs.ls_existing(["foo"])] == [ + EX_OK, [], "trash-put: trash regular empty file 'foo'? ", + [] + ] def test_when_file_does_not_exist(self): result = self.run_cmd(['trash-put', 'non-existent'], {"HOME": "/home/user"}, 123) - assert result == [ - ["trash-put: cannot trash non existent 'non-existent'"], - 'None', - EX_IOERR - ] + assert result.all() == [ + EX_IOERR, + ["trash-put: cannot trash non existent 'non-existent'"]] def test_when_file_does_not_exist_with_force(self): result = self.run_cmd(['trash-put', '-f', 'non-existent'], {"HOME": "/home/user"}, 123) - assert result == [[], 'None', 0] + assert result.all() == [EX_OK, []] def test_put_does_not_try_to_trash_non_existing_file(self): result = self.run_cmd(['trash-put', '-vvv', 'non-existing'], {"HOME": "/home/user"}, 123) - assert result == \ - [["trash-put: cannot trash non existent 'non-existing'"], 'None', - EX_IOERR] + assert result.all() == \ + [ + EX_IOERR, + ["trash-put: cannot trash non existent 'non-existing'"] + ] + + def test_exit_code_will_be_0_when_trash_succeeds(self): + self.fs.touch("pippo") + + result = self.run_cmd(['trash-put', 'pippo']) + + assert result.exit_code == EX_OK + + def test_exit_code_will_be_non_0_when_trash_fails(self): + self.fs.assert_does_not_exist("a") + + result = self.run_cmd(['trash-put', 'a']) + + assert result.exit_code == EX_IOERR + + def test_exit_code_will_be_non_0_when_just_one_trash_fails(self): + self.fs.touch("a") + self.fs.assert_does_not_exist("b") + self.fs.touch("c") + + result = self.run_cmd(['trash-put', 'a', 'b', 'c']) + + assert result.exit_code == EX_IOERR def test_when_there_is_no_working_trash_dir(self): self.fs.make_file("pippo") self.fs.makedirs('/.Trash-123', 0o000) - result = self.run_cmd(['trash-put', '-vvv', 'pippo'], {}, 123) + result = self.run_cmd(['trash-put', '-v', 'pippo'], {}, 123) - assert result[0] == [ - 'trash-put: volume of file: /', - 'trash-put: found unusable .Trash dir (should be a dir): /.Trash', - 'trash-put: trash directory is not secure: /.Trash/123', - 'trash-put: trying trash dir: /.Trash-123 from volume: /', - "trash-put: failed to trash pippo in /.Trash-123, because: [Errno 13] Permission denied: '/.Trash-123/files'", - "trash-put: cannot trash regular empty file 'pippo'", + assert result.all() == [ + EX_IOERR, + [ + "trash-put: cannot trash regular empty file 'pippo' (from volume '/')", + 'trash-put: `- failed to trash pippo in /.Trash/123, because trash dir cannot be created because its parent does not exists, trash-dir: /.Trash/123, parent: /.Trash', + "trash-put: `- failed to trash pippo in /.Trash-123, because error during directory creation: [Errno 13] Permission denied: '/.Trash-123/files'" + ] ] def test_multiple_volumes(self): @@ -73,20 +185,29 @@ self.fs.make_file("/disk1/pippo") self.is_mount.add_mount_point('/disk1') - result = self.run_cmd(['trash-put', '-vvv', '--home-fallback', + result = self.run_cmd(['trash-put', '-v', '--home-fallback', '/disk1/pippo'], {'HOME': '/home/user'}, 123) - assert result[0] == ['trash-put: volume of file: /disk1', - 'trash-put: trying trash dir: /home/user/.local/share/Trash from volume: /', - "trash-put: won't use trash dir ~/.local/share/Trash because its volume (/) in a different volume than /disk1/pippo (/disk1)", - 'trash-put: found unusable .Trash dir (should be a dir): /disk1/.Trash', - 'trash-put: trash directory is not secure: /disk1/.Trash/123', - 'trash-put: trying trash dir: /disk1/.Trash-123 from volume: /disk1', - "trash-put: failed to trash /disk1/pippo in /disk1/.Trash-123, because: [Errno 13] Permission denied: '/disk1/.Trash-123/files'", - 'trash-put: trying trash dir: /home/user/.local/share/Trash from volume: /', - "trash-put: trash dir not enabled: ~/.local/share/Trash", - "trash-put: cannot trash regular empty file '/disk1/pippo'"] + assert result[0] == ["trash-put: cannot trash regular empty file '/disk1/pippo' (from volume '/disk1')", + 'trash-put: `- failed to trash /disk1/pippo in /home/user/.local/share/Trash, because trash dir and file to be trashed are not in the same volume, trash-dir volume: /, file volume: /disk1', + 'trash-put: `- failed to trash /disk1/pippo in /disk1/.Trash/123, because trash dir cannot be created because its parent does not exists, trash-dir: /disk1/.Trash/123, parent: /disk1/.Trash', + "trash-put: `- failed to trash /disk1/pippo in /disk1/.Trash-123, because error during directory creation: [Errno 13] Permission denied: '/disk1/.Trash-123/files'", + 'trash-put: `- failed to trash /disk1/pippo in /home/user/.local/share/Trash, because home fallback not enabled'] + + def test_when_it_fails_to_prepare_trash_info_data(self): + flexmock.flexmock(self.fs).should_receive('parent_realpath2'). \ + and_raise(IOError, 'Corruption') + self.fs.make_file("foo") + + result = self.run_cmd(['trash-put', '-v', 'foo'], + {"HOME": "/home/user"}, 123) + assert result.all() == [ + EX_IOERR, + ["trash-put: cannot trash regular empty file 'foo' (from volume '/')", + 'trash-put: `- failed to trash foo in /home/user/.local/share/Trash, because failed to generate trashinfo content: Corruption', + 'trash-put: `- failed to trash foo in /.Trash/123, because trash dir cannot be created because its parent does not exists, trash-dir: /.Trash/123, parent: /.Trash', + 'trash-put: `- failed to trash foo in /.Trash-123, because failed to generate trashinfo content: Corruption']] def test_make_file(self): self.fs.make_file("pippo", 'content') @@ -128,8 +249,8 @@ actual = { 'file_pippo_exists': self.fs.exists("pippo"), - 'exit_code': result[2], - 'stderr': result[0], + 'exit_code': result.exit_code, + 'stderr': result.stderr, 'files_in_info_dir': self.fs.ls_aa( '/home/user/.local/share/Trash/info'), "content_of_trashinfo": self.fs.read_null( @@ -143,7 +264,10 @@ 'content_of_trashinfo': None, 'exit_code': EX_IOERR, 'stderr': [ - "trash-put: cannot trash regular file 'pippo'"], + "trash-put: cannot trash regular file 'pippo' (from volume '/')", + 'trash-put: `- failed to trash pippo in /home/user/.local/share/Trash, because failed to move pippo in /home/user/.local/share/Trash/files: No space left on device', + 'trash-put: `- failed to trash pippo in /.Trash/123, because trash dir cannot be created because its parent does not exists, trash-dir: /.Trash/123, parent: /.Trash', + 'trash-put: `- failed to trash pippo in /.Trash-123, because failed to move pippo in /.Trash-123/files: No space left on device'], 'file_pippo_exists': True, 'files_in_files_dir': [], 'files_in_info_dir': []} @@ -167,13 +291,28 @@ assert self.fs.read('/home/user/.local/share/Trash/files/pippo') \ == 'content' - def run_cmd(self, args, environ, uid): + def run_cmd(self, + args, # type: List[str] + environ=None, # type: Optional[Environ] + uid=None, # type: Optional[int] + ): # type: (...) -> Result + environ = environ or {} + uid = uid or 123 err = None exit_code = None try: - exit_code = self.cmd.run(args, environ, uid) + exit_code = self.cmd.run_put(args, environ, uid) except IOError as e: err = e stderr = self.stderr.getvalue().splitlines() - return [stderr, str(err), exit_code] + return Result(stderr, str(err), ensure_int(exit_code)) + + +class Result(NamedTuple('Result', [ + ('stderr', List[str]), + ('err', str), + ('exit_code', int), +])): + def all(self): + return [self.exit_code, self.stderr] diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_suffix.py trash-cli-0.23.11.10/tests/test_put/test_suffix.py --- trash-cli-0.23.2.13.2/tests/test_put/test_suffix.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_suffix.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,12 +1,10 @@ -import unittest - +from trashcli.put.core.int_generator import IntGenerator from trashcli.put.suffix import Suffix -class TestSuffix(unittest.TestCase): - def setUp(self): - self.randint = lambda x, y: "%s,%s" % (x,y) - self.suffix = Suffix(self.randint) +class TestSuffix: + def setup_method(self): + self.suffix = Suffix(InlineFakeIntGen(lambda x, y: "%s,%s" % (x, y))) def test_first_attempt(self): assert self.suffix.suffix_for_index(0) == '' @@ -16,3 +14,11 @@ def test_hundredth_attempt(self): assert self.suffix.suffix_for_index(100) == '_0,65535' + + +class InlineFakeIntGen(IntGenerator): + def __init__(self, func): + self.func = func + + def new_int(self, a, b): + return self.func(a, b) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_trash_dir_volume.py trash-cli-0.23.11.10/tests/test_put/test_trash_dir_volume.py --- trash-cli-0.23.2.13.2/tests/test_put/test_trash_dir_volume.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_trash_dir_volume.py 2023-11-10 07:15:04.000000000 +0000 @@ -2,13 +2,13 @@ from mock import Mock -from trashcli.fstab import create_fake_volume_of +from tests.support.fake_volume_of import fake_volume_of from trashcli.put.trash_dir_volume_reader import TrashDirVolumeReader class TestTrashDirVolume(unittest.TestCase): def setUp(self): - volumes = create_fake_volume_of(['/disk1', '/disk2']) + volumes = fake_volume_of(['/disk1', '/disk2']) fs = Mock() fs.realpath = lambda path: path self.trash_dir_volume = TrashDirVolumeReader(volumes, fs) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_trash_directories_finder.py trash-cli-0.23.11.10/tests/test_put/test_trash_directories_finder.py --- trash-cli-0.23.2.13.2/tests/test_put/test_trash_directories_finder.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_trash_directories_finder.py 2023-11-10 07:15:04.000000000 +0000 @@ -2,10 +2,10 @@ from mock import Mock -from trashcli.put.candidate import Candidate -from trashcli.put.gate import SameVolumeGate, HomeFallbackGate -from trashcli.put.path_maker import AbsolutePaths, RelativePaths -from trashcli.put.security_check import NoCheck, TopTrashDirCheck +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.check_type import NoCheck, TopTrashDirCheck +from trashcli.put.core.path_maker_type import PathMakerType +from trashcli.put.gate import Gate from trashcli.put.trash_directories_finder import TrashDirectoriesFinder @@ -24,18 +24,18 @@ assert result == [ Candidate(trash_dir_path='~/.local/share/Trash', volume='volume_of(~/.local/share/Trash)', - path_maker_type=AbsolutePaths, - check_type=NoCheck, gate=SameVolumeGate), + path_maker_type=PathMakerType.AbsolutePaths, + check_type=NoCheck, gate=Gate.SameVolume), Candidate(trash_dir_path='/volume/.Trash/123', volume='/volume', - path_maker_type=RelativePaths, - check_type=TopTrashDirCheck, gate=SameVolumeGate), + path_maker_type=PathMakerType.RelativePaths, + check_type=TopTrashDirCheck, gate=Gate.SameVolume), Candidate(trash_dir_path='/volume/.Trash-123', volume='/volume', - path_maker_type=RelativePaths, - check_type=NoCheck, gate=SameVolumeGate), + path_maker_type=PathMakerType.RelativePaths, + check_type=NoCheck, gate=Gate.SameVolume), Candidate(trash_dir_path='~/.local/share/Trash', volume='volume_of(~/.local/share/Trash)', - path_maker_type=AbsolutePaths, - check_type=NoCheck, gate=HomeFallbackGate), + path_maker_type=PathMakerType.AbsolutePaths, + check_type=NoCheck, gate=Gate.HomeFallback), ] def test_specific_user_dir(self): @@ -47,6 +47,6 @@ assert result == [('user_dir', 'volume_of(user_dir)', - RelativePaths, + PathMakerType.RelativePaths, NoCheck, - SameVolumeGate)] + Gate.SameVolume)] diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_trash_put_slow.py trash-cli-0.23.11.10/tests/test_put/test_trash_put_slow.py --- trash-cli-0.23.2.13.2/tests/test_put/test_trash_put_slow.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_trash_put_slow.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,186 +1,178 @@ # Copyright (C) 2009-2020 Andrea Francia Trivolzio(PV) Italy import os -import unittest from os.path import exists as file_exists +from typing import List +from typing import Optional import pytest -from trashcli.fs import read_file from tests import run_command -from tests.support.asserts import assert_line_in_text +from tests.run_command import temp_dir from tests.support.files import make_empty_file, require_empty_dir, \ make_sticky_dir from tests.support.my_path import MyPath +from trashcli.fs import read_file +from trashcli.lib.environ import Environ -class TrashPutFixture: - - def __init__(self): - self.temp_dir = MyPath.make_temp_dir() - - def run_trashput(self, *args): - self.environ = {'XDG_DATA_HOME': self.temp_dir / 'XDG_DATA_HOME', - 'HOME': self.temp_dir / 'home'} - result = run_command.run_command(self.temp_dir, - "trash-put", - list(args), - env=self.environ) - self.stdout = result.stdout - self.stderr = result.stderr - self.exit_code = result.exit_code - - def __del__(self): - self.temp_dir.clean_up() +@pytest.fixture +def runner(temp_dir): + return Runner(temp_dir) + + +class Runner: + def __init__(self, cwd): + self.cwd = cwd + + def run_trashput(self, + args, # type: List[str] + env=None, # type: Optional[Environ] + ): # type: (...) -> run_command.CmdResult + env = env or {} + env['TRASH_PUT_FAKE_UID_FOR_TESTING'] = '123' + return run_command.run_command(self.cwd, + "trash-put", + list(args), + env=env) @pytest.mark.slow -class TestDeletingExistingFile(unittest.TestCase): - def setUp(self): - self.temp_dir = MyPath.make_temp_dir() - env = {'XDG_DATA_HOME': self.temp_dir / 'XDG_DATA_HOME' } - make_empty_file(self.temp_dir / 'foo') - self.result = run_command.run_command(self.temp_dir, "trash-put", - [self.temp_dir / 'foo'], - env=env) - - def test_it_should_remove_the_file(self): - assert not file_exists(self.temp_dir / 'foo') - - def test_it_should_remove_it_silently(self): - self.assertEqual("", self.result.stdout) +class TestDeletingExistingFile: - def test_a_trashinfo_file_should_have_been_created(self): - read_file(self.temp_dir / 'XDG_DATA_HOME/Trash/info/foo.trashinfo') + @pytest.fixture + def trash_foo(self, temp_dir, runner): + make_empty_file(temp_dir / 'foo') + result = runner.run_trashput([temp_dir / 'foo'], env={ + 'XDG_DATA_HOME': temp_dir / 'XDG_DATA_HOME'}) + yield result + + def test_it_should_remove_the_file(self, temp_dir, trash_foo): + assert file_exists(temp_dir / 'foo') is False + + def test_it_should_remove_it_silently(self, trash_foo): + assert trash_foo.stdout == '' + + def test_a_trashinfo_file_should_have_been_created(self, temp_dir, + trash_foo): + read_file(temp_dir / 'XDG_DATA_HOME/Trash/info/foo.trashinfo') - def tearDown(self): - self.temp_dir.clean_up() @pytest.mark.slow -class Test_when_deleting_an_existing_file_in_verbose_mode(unittest.TestCase): - def setUp(self): - self.fixture = TrashPutFixture() - self.foo_file = self.fixture.temp_dir / "foo" - make_empty_file(self.foo_file) - self.fixture.run_trashput('-v', self.foo_file) - - def test_should_tell_where_a_file_is_trashed(self): - output = self.fixture.stderr.splitlines() - expected_line = "trash-put: '%s' trashed in %s/XDG_DATA_HOME/Trash" % \ - (self.foo_file, self.fixture.temp_dir) - assert (expected_line in output) +class TestWhenDeletingAnExistingFileInVerboseMode: + @pytest.fixture + def run_trashput(self, temp_dir, runner): + make_empty_file(temp_dir / "foo") + return runner.run_trashput(["-v", temp_dir / "foo"], env={ + 'XDG_DATA_HOME': temp_dir / 'XDG_DATA_HOME', + 'HOME': temp_dir / 'home'}) - def test_should_be_succesfull(self): - assert 0 == self.fixture.exit_code + def test_should_tell_where_a_file_is_trashed(self, temp_dir, run_trashput): + output = run_trashput.clean_tmp_and_grep(temp_dir, "trashed in") + assert "trash-put: '/foo' trashed in /XDG_DATA_HOME/Trash" in output -@pytest.mark.slow -class Test_when_deleting_a_non_existing_file(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - self.fixture = TrashPutFixture() - - def test_should_be_succesfull(self): - self.fixture.run_trashput('-v', self.tmp_dir / 'non-existent') - assert 0 != self.fixture.exit_code - - def tearDown(self): - self.tmp_dir.clean_up() + def test_should_be_successful(self, run_trashput): + assert 0 == run_trashput.exit_code @pytest.mark.slow -class Test_when_fed_with_dot_arguments(unittest.TestCase): +class TestWhenDeletingANonExistingFile: + def test_should_be_succesfull(self, temp_dir, runner): + result = runner.run_trashput(['-v', temp_dir / 'non-existent']) + assert 0 != result.exit_code - def setUp(self): - self.fixture = TrashPutFixture() - def test_dot_argument_is_skipped(self): +@pytest.mark.slow +class TestWhenFedWithDotArguments: - self.fixture.run_trashput(".") + def test_dot_argument_is_skipped(self, temp_dir, runner): + result = runner.run_trashput(["."]) # the dot directory shouldn't be operated, but a diagnostic message # shall be written on stderr - self.assertEqual("trash-put: cannot trash directory '.'\n", - self.fixture.stderr) + assert result.stderr == "trash-put: cannot trash directory '.'\n" - def test_dot_dot_argument_is_skipped(self): - - self.fixture.run_trashput("..") + def test_dot_dot_argument_is_skipped(self, temp_dir, runner): + result = runner.run_trashput([".."]) # the dot directory shouldn't be operated, but a diagnostic message # shall be written on stderr - self.assertEqual("trash-put: cannot trash directory '..'\n", - self.fixture.stderr) + assert result.stderr == "trash-put: cannot trash directory '..'\n" - def test_dot_argument_is_skipped_even_in_subdirs(self): + def test_dot_argument_is_skipped_even_in_subdirs(self, temp_dir, runner): sandbox = MyPath.make_temp_dir() - self.fixture.run_trashput("%s/." % sandbox) + result = runner.run_trashput(["%s/." % sandbox]) # the dot directory shouldn't be operated, but a diagnostic message # shall be written on stderr - self.assertEqual("trash-put: cannot trash '.' directory '%s/.'\n" % - sandbox, - self.fixture.stderr) - + assert "trash-put: cannot trash '.' directory '%s/.'\n" % sandbox == \ + result.stderr assert file_exists(sandbox) sandbox.clean_up() - def test_dot_dot_argument_is_skipped_even_in_subdirs(self): + def test_dot_dot_argument_is_skipped_even_in_subdirs(self, temp_dir, + runner): sandbox = MyPath.make_temp_dir() - self.fixture.run_trashput("%s/.." % sandbox) + result = runner.run_trashput(["%s/.." % sandbox]) # the dot directory shouldn't be operated, but a diagnostic message # shall be written on stderr - self.assertEqual("trash-put: cannot trash '..' directory '%s/..'\n" % - sandbox, - self.fixture.stderr) - + assert result.stderr == ( + "trash-put: cannot trash '..' directory '%s/..'\n" % sandbox) assert file_exists(sandbox) sandbox.clean_up() @pytest.mark.slow -class TestUnsecureTrashDirMessages(unittest.TestCase): - def setUp(self): - self.temp_dir = MyPath.make_temp_dir() - self.fake_vol = self.temp_dir / 'fake-vol' - self.fixture = TrashPutFixture() - require_empty_dir(self.fake_vol) - make_empty_file(self.fake_vol / 'foo') - - def test_when_is_unsticky(self): - require_empty_dir(self.fake_vol / '.Trash') - - self.fixture.run_trashput('--force-volume', self.fake_vol, - '-v', - self.fake_vol / 'foo') - - assert_line_in_text( - 'trash-put: found unsecure .Trash dir (should be sticky): ' + - self.fake_vol / '.Trash', self.fixture.stderr) - - def test_when_it_is_not_a_dir(self): - make_empty_file(self.fake_vol / '.Trash') - - self.fixture.run_trashput('--force-volume', self.fake_vol, - '-v', - self.fake_vol / 'foo') - - assert_line_in_text( - 'trash-put: found unusable .Trash dir (should be a dir): ' + - self.fake_vol / '.Trash', self.fixture.stderr) - - def test_when_is_a_symlink(self): - make_sticky_dir( self.fake_vol / 'link-destination') - os.symlink('link-destination', self.fake_vol / '.Trash') - - self.fixture.run_trashput('--force-volume', self.fake_vol, - '-v', self.fake_vol / 'foo') - - assert_line_in_text( - 'trash-put: found unsecure .Trash dir (should not be a symlink): ' + - self.fake_vol / '.Trash', self.fixture.stderr) +class TestUnsecureTrashDirMessages: - def tearDown(self): - self.temp_dir.clean_up() + @pytest.fixture + def fake_vol(self, temp_dir): + vol = temp_dir / 'fake-vol' + require_empty_dir(vol) + return vol + + def test_when_is_unsticky(self, temp_dir, fake_vol, runner): + make_empty_file(fake_vol / 'foo') + require_empty_dir(fake_vol / '.Trash') + + result = runner.run_trashput(['--force-volume', + fake_vol, + '-v', + fake_vol / 'foo']) + + assert result.clean_vol_and_grep('/.Trash/123', fake_vol) == [ + 'trash-put: `- failed to trash /vol/foo in /vol/.Trash/123, because trash ' + 'dir is insecure, its parent should be sticky, trash-dir: /vol/.Trash/123, ' + 'parent: /vol/.Trash' + ] + + def test_when_it_is_not_a_dir(self, fake_vol, runner, temp_dir): + make_empty_file(fake_vol / 'foo') + make_empty_file(fake_vol / '.Trash') + + result = runner.run_trashput(['--force-volume', + fake_vol, + '-v', + fake_vol / 'foo']) + + assert result.clean_vol_and_grep('/.Trash/123', fake_vol) == [ + 'trash-put: `- failed to trash /vol/foo in /vol/.Trash/123, because trash ' + 'dir cannot be created as its parent is a file instead of being a directory, ' + 'trash-dir: /vol/.Trash/123, parent: /vol/.Trash' + ] + + def test_when_is_a_symlink(self, fake_vol, temp_dir, runner): + make_empty_file(fake_vol / 'foo') + make_sticky_dir(fake_vol / 'link-destination') + os.symlink('link-destination', fake_vol / '.Trash') + + result = runner.run_trashput(['--force-volume', + fake_vol, '-v', fake_vol / 'foo']) + + assert result.clean_vol_and_grep("insecure", fake_vol) == [ + 'trash-put: `- failed to trash /vol/foo in /vol/.Trash/123, because ' + 'trash dir is insecure, its parent should not be a symlink, trash-dir: ' + '/vol/.Trash/123, parent: /vol/.Trash'] diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_trasher.py trash-cli-0.23.11.10/tests/test_put/test_trasher.py --- trash-cli-0.23.2.13.2/tests/test_put/test_trasher.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_trasher.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,126 +0,0 @@ -import unittest - -from typing import cast - -import flexmock -from mock import Mock, call - -from trashcli.put.my_logger import LogData -from trashcli.put.parser import mode_force, mode_interactive -from trashcli.put.fs.real_fs import RealFs -from trashcli.put.reporter import TrashPutReporter -from trashcli.put.trash_result import TrashResult -from trashcli.put.trasher import Trasher -from trashcli.put.user import user_replied_no, user_replied_yes - - -class TestTrasher(unittest.TestCase): - def setUp(self): - self.file_trasher = Mock(spec=['trash_file']) - self.user = Mock() - self.reporter = Mock(spec=['unable_to_trash_dot_entries']) - self.fs = flexmock.Mock(spec=RealFs) - self.fs.should_receive('is_accessible').and_return(True) - self.fs.should_receive('lexists').and_return(True) - self.trasher = Trasher(self.file_trasher, self.user, - cast(TrashPutReporter, self.reporter), - cast(RealFs, self.fs)) - self.file_trasher.trash_file.return_value = 'file_trasher result' - - def test(self): - result = self.trasher.trash('file', - 'user-trash-dir', - cast(TrashResult, 'result'), - mode_force, - 'forced_volume', - 'home_fallback', - 'program_name', - cast(LogData,'log_data'), - {"env": "ironment"}, - 123) - - assert [self.file_trasher.mock_calls, - result] == \ - [ - [call.trash_file( - 'file', - 'forced_volume', - 'user-trash-dir', - 'home_fallback', - 'result', - {"env": "ironment"}, - 123, - 'log_data', - )], - 'file_trasher result' - ] - - def test_interactive_yes(self): - self.user.ask_user_about_deleting_file.return_value = user_replied_yes - - result = self.trasher.trash('file', - 'user-trash-dir', - 'result', - mode_interactive, - 'forced_volume', - 'home_fallback', - 'program_name', - cast(LogData, 'log_data'), - {"env": "ironment"}, - 123) - - assert [self.user.mock_calls, - self.file_trasher.mock_calls, - result] == \ - [ - [call.ask_user_about_deleting_file('program_name', 'file')], - [call.trash_file( - 'file', - 'forced_volume', - 'user-trash-dir', - 'home_fallback', - 'result', - {"env": "ironment"}, - 123, - 'log_data', - )], - 'file_trasher result' - ] - - def test_interactive_no(self): - self.user.ask_user_about_deleting_file.return_value = user_replied_no - - result = self.trasher.trash('file', - 'user-trash-dir', - 'result', - mode_interactive, - 'forced_volume', - 'home_fallback', - 'program_name', - {}, - 123, - 99) - - assert [self.user.mock_calls, - self.file_trasher.mock_calls, - result] == \ - [ - [call.ask_user_about_deleting_file('program_name', 'file')], - [], - 'result' - ] - - def test_dot_entry(self): - self.trasher.trash('.', - 'user-trash-dir', - 'result', - False, - 'forced_volume', - 'home_fallback', - 'program_name', - {}, - 123, - 99) - - assert self.reporter.mock_calls == \ - [call.unable_to_trash_dot_entries('.', 'program_name')] diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_trashing_checker.py trash-cli-0.23.11.10/tests/test_put/test_trashing_checker.py --- trash-cli-0.23.2.13.2/tests/test_put/test_trashing_checker.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_trashing_checker.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,74 +0,0 @@ -import unittest - -import flexmock -from typing import cast - -from .support.fake_fs.fake_fs import FakeFs -from trashcli.put.candidate import Candidate -from trashcli.put.gate import SameVolumeGate, ClosedGate, HomeFallbackGate -from trashcli.put.gate_impl import ClosedGateImpl, HomeFallbackGateImpl, \ - SameVolumeGateImpl -from trashcli.put.trash_dir_volume_reader import TrashDirVolumeReader -from trashcli.put.trashee import Trashee -from trashcli.put.trashing_checker import TrashingChecker - - -class Value: - def __init__(self, values): - self.__dict__ = values - - -def mock_value(type, **kwargs): - return cast(type, Value(kwargs)) - - -class TestTrashingChecker(unittest.TestCase): - def setUp(self): - self.fs = FakeFs() - self.trash_dir_volume = flexmock.Mock() - self.checker = TrashingChecker( - { - ClosedGate: ClosedGateImpl(), - HomeFallbackGate: HomeFallbackGateImpl(self.fs), - SameVolumeGate: SameVolumeGateImpl( - cast(TrashDirVolumeReader, self.trash_dir_volume)), - }) - - def test_trashing_checker_same(self): - self.trash_dir_volume.should_receive('volume_of_trash_dir') \ - .with_args('trash-dir-path').and_return('/volume1') - - result = self.checker.file_could_be_trashed_in( - Trashee('/path1', '/volume1'), - make_candidate('trash-dir-path', SameVolumeGate, '/disk1'), - {}) - - assert result.ok is True - - def test_home_in_same_volume(self): - result = self.checker.file_could_be_trashed_in( - Trashee('/path1', '/volume1'), - make_candidate('trash-dir-path', HomeFallbackGate, '/disk1'), - {}) - - assert result.ok is False - - def test_trashing_checker_different(self): - self.trash_dir_volume.should_receive('volume_of_trash_dir') \ - .with_args('trash-dir-path').and_return('/volume2') - - result = self.checker.file_could_be_trashed_in( - Trashee('/path1', '/volume1'), - make_candidate('trash-dir-path', SameVolumeGate, '/disk1'), - {}) - - assert result.ok is False - assert result.reason == "won't use trash dir trash-dir-path because its volume (/disk1) in a different volume than /path1 (/volume1)" - - -def make_candidate(trash_dir_path, gate, volume): - return Candidate(trash_dir_path=trash_dir_path, - path_maker_type=None, - check_type=None, - gate=gate, - volume=volume) diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_user.py trash-cli-0.23.11.10/tests/test_put/test_user.py --- trash-cli-0.23.2.13.2/tests/test_put/test_user.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_user.py 2023-11-10 07:15:04.000000000 +0000 @@ -2,8 +2,8 @@ from typing import cast import flexmock -import mock +from trashcli.lib.my_input import HardCodedInput from trashcli.put.describer import Describer from trashcli.put.user import ( User, @@ -15,8 +15,7 @@ class TestUser(unittest.TestCase): def setUp(self): - self.my_input = mock.Mock() - self.my_input.return_value = "y" + self.my_input = HardCodedInput("y") self.describer = flexmock.Mock(spec=Describer) self.describer.should_receive('describe').and_return("description!") diff -Nru trash-cli-0.23.2.13.2/tests/test_put/test_volume_of_parent.py trash-cli-0.23.11.10/tests/test_put/test_volume_of_parent.py --- trash-cli-0.23.2.13.2/tests/test_put/test_volume_of_parent.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/test_volume_of_parent.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,26 +1,26 @@ import unittest +from typing import cast import flexmock -from typing import cast -from tests.support.volumes_mock import volumes_mock -from trashcli.fstab import Volumes -from trashcli.put.fs.parent_realpath import ParentRealpath +from trashcli.fstab.volume_of import VolumeOf +from trashcli.put.fs.parent_realpath import ParentRealpathFs from trashcli.put.fs.volume_of_parent import VolumeOfParent class TestVolumeOfParent(unittest.TestCase): def setUp(self): - self.volumes = flexmock.Mock(spec=Volumes) - self.parent_realpath = flexmock.Mock(spec=ParentRealpath) - self.volume_of_parent = VolumeOfParent(cast(Volumes, self.volumes), - cast(ParentRealpath, - self.parent_realpath)) + self.volumes = flexmock.Mock(spec=VolumeOf) + self.parent_realpath_fs = flexmock.Mock(spec=ParentRealpathFs) + self.volume_of_parent = VolumeOfParent(cast(VolumeOf, self.volumes), + cast(ParentRealpathFs, + self.parent_realpath_fs)) + def test(self): - self.parent_realpath.should_receive('parent_realpath').\ - with_args('/path/to/file').\ + self.parent_realpath_fs.should_receive('parent_realpath'). \ + with_args('/path/to/file'). \ and_return('parent-realpath') - self.volumes.should_receive('volume_of').with_args("parent-realpath").\ + self.volumes.should_receive('volume_of').with_args("parent-realpath"). \ and_return('volume-of-parent') result = self.volume_of_parent.volume_of_parent('/path/to/file') diff -Nru trash-cli-0.23.2.13.2/tests/test_put/trashing_checker/test_home_fallback_gate_impl.py trash-cli-0.23.11.10/tests/test_put/trashing_checker/test_home_fallback_gate_impl.py --- trash-cli-0.23.2.13.2/tests/test_put/trashing_checker/test_home_fallback_gate_impl.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/trashing_checker/test_home_fallback_gate_impl.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,47 @@ +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.check_type import NoCheck +from trashcli.put.core.either import Left +from trashcli.put.core.path_maker_type import PathMakerType +from trashcli.put.core.trashee import Trashee +from trashcli.put.gate import Gate +from trashcli.put.janitor_tools.trash_dir_checker import TrashDirChecker, \ + make_ok, HomeFallBackNotEnabled +from ..support.fake_fs.fake_fs import FakeFs + + +class TestHomeFallbackGate: + def setup_method(self): + self.fake_fs = FakeFs() + self.gate_impl = TrashDirChecker(self.fake_fs, "volumes") + + def test_not_enabled(self): + result = self.gate_impl.file_could_be_trashed_in( + make_trashee(), + make_candidate('/xdf/Trash'), + {}) + assert result == Left(HomeFallBackNotEnabled()) + + def test_enabled(self): + result = self.gate_impl.file_could_be_trashed_in( + make_trashee(), + make_candidate('/xdf/Trash'), + { + "TRASH_ENABLE_HOME_FALLBACK": "1" + }) + assert result == make_ok() + + # def test(self): + # result = os.statvfs('/Users/andrea/trash-cli') + # print("") + # pprint(result.f_bavail / 1024 / 1024) + # pprint(result.f_bfree / 1024 / 1024) + # # pprint(psutil.disk_usage('/')) + + +def make_candidate(path): + return Candidate(path, '/disk2', PathMakerType.AbsolutePaths, NoCheck, + Gate.HomeFallback) + + +def make_trashee(): + return Trashee('/disk1/foo', "/disk1") diff -Nru trash-cli-0.23.2.13.2/tests/test_put/trashing_checker/test_trashing_checker.py trash-cli-0.23.11.10/tests/test_put/trashing_checker/test_trashing_checker.py --- trash-cli-0.23.2.13.2/tests/test_put/trashing_checker/test_trashing_checker.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_put/trashing_checker/test_trashing_checker.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,52 @@ +from trashcli.fstab.volumes import FakeVolumes +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.either import Left +from trashcli.put.core.trashee import Trashee +from trashcli.put.gate import Gate +from trashcli.put.janitor_tools.trash_dir_checker import TrashDirChecker, \ + DifferentVolumes +from ..support.fake_fs.fake_fs import FakeFs + + +class TestTrashingChecker: + def setup_method(self): + self.fs = FakeFs() + self.volumes = FakeVolumes(["/"]) + self.checker = TrashDirChecker(self.fs, self.volumes) + + def test_trashing_checker_same(self): + self.volumes.add_volume('/volume1') + + result = self.checker.file_could_be_trashed_in( + Trashee('/path1', '/volume1'), + make_candidate('/volume1/trash-dir', Gate.SameVolume), + {}) + + assert result.is_valid() is True + + def test_home_in_same_volume(self): + + result = self.checker.file_could_be_trashed_in( + Trashee('/path1', '/volume1'), + make_candidate('/home-vol/trash-dir', Gate.HomeFallback), + {}) + + assert result.is_valid() is False + + def test_trashing_checker_different(self): + self.volumes.add_volume("/vol1") + self.volumes.add_volume("/vol2") + + result = self.checker.file_could_be_trashed_in( + Trashee('/path1', '/vol1'), + make_candidate('/vol2/trash-dir-path', Gate.SameVolume), + {}) + + assert result == Left(DifferentVolumes("/vol2", "/vol1")) + +def make_candidate(trash_dir_path, gate): + return Candidate(trash_dir_path=trash_dir_path, + path_maker_type=None, + check_type=None, + gate=gate, + volume="ignored") diff -Nru trash-cli-0.23.2.13.2/tests/test_removing_files/test_file_remover.py trash-cli-0.23.11.10/tests/test_removing_files/test_file_remover.py --- trash-cli-0.23.2.13.2/tests/test_removing_files/test_file_remover.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_removing_files/test_file_remover.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,16 @@ +import unittest + +from trashcli.rm.file_remover import FileRemover + + +try: + FileNotFoundError +except NameError: + FileNotFoundError = OSError # python 2 + + +class TestFileRemover(unittest.TestCase): + def test_remove_file_fails_when_file_does_not_exists(self): + file_remover = FileRemover() + self.assertRaises(FileNotFoundError, file_remover.remove_file2, + '/non/existing/path') diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/arg_parser/test_restore_arg_parser.py trash-cli-0.23.11.10/tests/test_restore/arg_parser/test_restore_arg_parser.py --- trash-cli-0.23.2.13.2/tests/test_restore/arg_parser/test_restore_arg_parser.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/arg_parser/test_restore_arg_parser.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,43 @@ +import unittest + +from trashcli.lib.print_version import PrintVersionArgs +from trashcli.restore.args import RunRestoreArgs, Sort +from trashcli.restore.restore_arg_parser import RestoreArgParser + + +class TestRestoreArgs(unittest.TestCase): + def setUp(self): + self.parser = RestoreArgParser() + + def test_default_path(self): + args = self.parser.parse_restore_args([''], "curdir") + + self.assertEqual(RunRestoreArgs(path='curdir', + sort=Sort.ByDate, + trash_dir=None, + overwrite=False), + args) + + def test_path_specified_relative_path(self): + args = self.parser.parse_restore_args(['', 'path'], "curdir") + + self.assertEqual(RunRestoreArgs(path='curdir/path', + sort=Sort.ByDate, + trash_dir=None, + overwrite=False), + args) + + def test_path_specified_fullpath(self): + args = self.parser.parse_restore_args(['', '/a/path'], "ignored") + + self.assertEqual(RunRestoreArgs(path='/a/path', + sort=Sort.ByDate, + trash_dir=None, + overwrite=False), + args) + + def test_show_version(self): + args = self.parser.parse_restore_args(['program', '--version'], + "ignored") + + self.assertEqual(PrintVersionArgs(argv0='program'), args) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_all_trash_directories.py trash-cli-0.23.11.10/tests/test_restore/collaborators/test_all_trash_directories.py --- trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_all_trash_directories.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/collaborators/test_all_trash_directories.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,24 @@ +import unittest + +from trashcli.fstab.volumes import FakeVolumes2 +from trashcli.restore.trash_directories import TrashDirectories1 + + +class TestTrashDirectories(unittest.TestCase): + def setUp(self): + environ = {'HOME': '~'} + self.volumes = FakeVolumes2("volume_of(%s)", []) + self.trash_directories = TrashDirectories1(self.volumes, 123, environ) + + def test_list_all_directories(self): + self.volumes.set_volumes(['/', '/mnt']) + + result = list(self.trash_directories.all_trash_directories()) + + assert ([ + ('~/.local/share/Trash', 'volume_of(~/.local/share/Trash)'), + ('/.Trash/123', '/'), + ('/.Trash-123', '/'), + ('/mnt/.Trash/123', '/mnt'), + ('/mnt/.Trash-123', '/mnt')] == + result) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_is_trashed_from_path.py trash-cli-0.23.11.10/tests/test_restore/collaborators/test_is_trashed_from_path.py --- trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_is_trashed_from_path.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/collaborators/test_is_trashed_from_path.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,17 @@ +import unittest + +from trashcli.restore.run_restore_action import original_location_matches_path + + +class TestOriginalLocationMatchesPath(unittest.TestCase): + def test1(self): + assert original_location_matches_path("/full/path", "/full") == True + + def test2(self): + assert original_location_matches_path("/full/path", "/full/path") == True + + def test3(self): + assert original_location_matches_path("/prefix-extension", "/prefix") == False + + def test_root(self): + assert original_location_matches_path("/any/path", "/") == True diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_parse_indexes.py trash-cli-0.23.11.10/tests/test_restore/collaborators/test_parse_indexes.py --- trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_parse_indexes.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/collaborators/test_parse_indexes.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,46 @@ +import unittest + +import six + +from trashcli.restore.range import Range +from trashcli.restore.restore_asking_the_user import InvalidEntry, parse_indexes +from trashcli.restore.sequences import Sequences +from trashcli.restore.single import Single + + +class TestParseIndexes(unittest.TestCase): + def test_non_numeric(self): + with six.assertRaisesRegex(self, InvalidEntry, "^not an index: a$"): + parse_indexes("a", 10) + + def test(self): + with six.assertRaisesRegex(self, InvalidEntry, "^out of range 0..9: 10$"): + parse_indexes("10", 10) + + def test2(self): + self.assertEqual(Sequences([Single(9)]), parse_indexes("9", 10)) + + def test3(self): + self.assertEqual(Sequences([Single(0)]), parse_indexes("0", 10)) + + def test4(self): + assert Sequences([Range(1, 4)]) == parse_indexes("1-4", 10) + + def test5(self): + self.assertEqual(Sequences([Single(1), + Single(2), + Single(3), + Single(4)]), + parse_indexes("1,2,3,4", 10)) + + def test_interval_without_start(self): + with six.assertRaisesRegex(self, InvalidEntry, "^open interval: -1$"): + parse_indexes("-1", 10) + + def test_interval_without_end(self): + with six.assertRaisesRegex(self, InvalidEntry, "^open interval: 1-$"): + parse_indexes("1-", 10) + + def test_complex(self): + indexes = parse_indexes("1-5,7", 10) + self.assertEqual(Sequences([Range(1, 5), Single(7)]), indexes) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_restore_asking_the_user.py trash-cli-0.23.11.10/tests/test_restore/collaborators/test_restore_asking_the_user.py --- trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_restore_asking_the_user.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/collaborators/test_restore_asking_the_user.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,42 @@ +import unittest + +from mock import Mock, call + +from trashcli.lib.my_input import HardCodedInput +from trashcli.restore.output_event import Quit +from trashcli.restore.output_recorder import OutputRecorder +from trashcli.restore.restore_asking_the_user import RestoreAskingTheUser +from trashcli.restore.restorer import Restorer + + +class TestRestoreAskingTheUser(unittest.TestCase): + def setUp(self): + self.input = HardCodedInput() + self.restorer = Mock(spec=Restorer) + self.output = OutputRecorder() + self.asking_user = RestoreAskingTheUser(self.input, + self.restorer, + self.output) + + def test(self): + self.input.set_reply('0') + + self.asking_user.restore_asking_the_user(['trashed_file1', + 'trashed_file2'], False) + + self.assertEqual('What file to restore [0..1]: ', + self.input.last_prompt()) + self.assertEqual([call.restore_trashed_file('trashed_file1', False)], + self.restorer.mock_calls) + self.assertEqual([], self.output.events) + + def test2(self): + self.input.raise_exception(KeyboardInterrupt) + + self.asking_user.restore_asking_the_user(['trashed_file1', + 'trashed_file2'], False) + + self.assertEqual('What file to restore [0..1]: ', + self.input.last_prompt()) + self.assertEqual([], self.restorer.mock_calls) + self.assertEqual([Quit()], self.output.events) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_sequences.py trash-cli-0.23.11.10/tests/test_restore/collaborators/test_sequences.py --- trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_sequences.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/collaborators/test_sequences.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,10 @@ +import unittest + +from trashcli.restore.restore_asking_the_user import parse_indexes + + +class TestSequences(unittest.TestCase): + def test(self): + sequences = parse_indexes("1-5,7", 10) + result = [index for index in sequences.all_indexes()] + self.assertEqual([1, 2, 3, 4, 5, 7], result) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_trash_directories2.py trash-cli-0.23.11.10/tests/test_restore/collaborators/test_trash_directories2.py --- trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_trash_directories2.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/collaborators/test_trash_directories2.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,39 @@ +import unittest + +import pytest +from mock import Mock, call + +from tests.support.fake_volume_of import volume_of_stub +from trashcli.restore.trash_directories import TrashDirectories2 + + +@pytest.mark.slow +class TestTrashDirectories2(unittest.TestCase): + def setUp(self): + self.trash_directories = Mock(spec=['all_trash_directories']) + self.volumes = volume_of_stub(lambda x: "volume_of(%s)" % x) + self.trash_directories2 = TrashDirectories2( + self.volumes, + self.trash_directories, + ) + + def test_when_user_dir_is_none(self): + self.trash_directories.all_trash_directories.return_value = \ + "os-trash-directories" + + result = self.trash_directories2.trash_directories_or_user(None) + + self.assertEqual([call.all_trash_directories()], + self.trash_directories.mock_calls) + self.assertEqual('os-trash-directories', result) + + def test_when_user_dir_is_specified(self): + self.trash_directories.all_trash_directories.return_value = \ + "os-trash-directories" + + result = self.trash_directories2.trash_directories_or_user( + 'user-trash_dir') + + self.assertEqual([], self.trash_directories.mock_calls) + self.assertEqual([('user-trash_dir', 'volume_of(user-trash_dir)')], + result) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_trash_directory.py trash-cli-0.23.11.10/tests/test_restore/collaborators/test_trash_directory.py --- trash-cli-0.23.2.13.2/tests/test_restore/collaborators/test_trash_directory.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/collaborators/test_trash_directory.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,53 @@ +import unittest + +import pytest +import six + +from tests.support.files import make_file, require_empty_dir +from tests.support.my_path import MyPath +from trashcli.restore.file_system import RealListingFileSystem +from trashcli.restore.info_files import InfoFiles + + +@pytest.mark.slow +class TestTrashDirectory(unittest.TestCase): + def setUp(self): + self.temp_dir = MyPath.make_temp_dir() + require_empty_dir(self.temp_dir / 'trash-dir') + self.info_files = InfoFiles(RealListingFileSystem()) + + def test_should_list_a_trashinfo(self): + make_file(self.temp_dir / 'trash-dir/info/foo.trashinfo') + + result = self.list_trashinfos() + + assert [('trashinfo', self.temp_dir / 'trash-dir/info/foo.trashinfo')] == result + + def test_should_list_multiple_trashinfo(self): + make_file(self.temp_dir / 'trash-dir/info/foo.trashinfo') + make_file(self.temp_dir / 'trash-dir/info/bar.trashinfo') + make_file(self.temp_dir / 'trash-dir/info/baz.trashinfo') + + result = self.list_trashinfos() + + six.assertCountEqual(self, + [('trashinfo', self.temp_dir / 'trash-dir/info/foo.trashinfo'), + ('trashinfo', self.temp_dir / 'trash-dir/info/baz.trashinfo'), + ('trashinfo', self.temp_dir / 'trash-dir/info/bar.trashinfo')], + result) + + def test_non_trashinfo_should_reported_as_a_warn(self): + make_file(self.temp_dir / 'trash-dir/info/not-a-trashinfo') + + result = self.list_trashinfos() + + six.assertCountEqual(self, + [('non_trashinfo', + self.temp_dir / 'trash-dir/info/not-a-trashinfo')], + result) + + def list_trashinfos(self): + return list(self.info_files.all_info_files(self.temp_dir / 'trash-dir')) + + def tearDown(self): + self.temp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/end_to_end/test_end_to_end_restore.py trash-cli-0.23.11.10/tests/test_restore/end_to_end/test_end_to_end_restore.py --- trash-cli-0.23.2.13.2/tests/test_restore/end_to_end/test_end_to_end_restore.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/end_to_end/test_end_to_end_restore.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,89 @@ +import os +import unittest +from datetime import datetime +from os.path import exists as file_exists, join as pj + +import pytest + +from tests import run_command +from tests.fake_trash_dir import FakeTrashDir +from tests.support.my_path import MyPath +from trashcli.fs import read_file + + +@pytest.mark.slow +class TestEndToEndRestore(unittest.TestCase): + def setUp(self): + self.tmp_dir = MyPath.make_temp_dir() + self.curdir = self.tmp_dir / "cwd" + self.trash_dir = self.tmp_dir / "trash-dir" + os.makedirs(self.curdir) + self.fake_trash_dir = FakeTrashDir(self.trash_dir) + + def test_no_file_trashed(self): + result = self.run_command("trash-restore") + + self.assertEqual("""\ +No files trashed from current dir ('%s') +""" % self.curdir, result.output()) + + def test_original_file_not_existing(self): + self.fake_trash_dir.add_trashinfo3("foo", "/path", datetime(2000,1,1,0,0,1)) + + result = self.run_command("trash-restore", ["/"], input='0') + + self.assertEqual(" 0 2000-01-01 00:00:01 /path\n" + "What file to restore [0..0]: \n" + "[Errno 2] No such file or directory: '%s/files/foo'\n" % + self.trash_dir, + result.output()) + + def test_restore_happy_path(self): + self.fake_trash_dir.add_trashed_file( + "file1", pj(self.curdir, "path", "to", "file1"), "contents") + self.fake_trash_dir.add_trashed_file( + "file2", pj(self.curdir, "path", "to", "file2"), "contents") + self.assertEqual(True, file_exists(pj(self.trash_dir, "info", "file2.trashinfo"))) + self.assertEqual(True, file_exists(pj(self.trash_dir, "files", "file2"))) + + result = self.run_command("trash-restore", ["/", '--sort=path'], input='1') + + self.assertEqual("""\ + 0 2000-01-01 00:00:01 %(curdir)s/path/to/file1 + 1 2000-01-01 00:00:01 %(curdir)s/path/to/file2 +What file to restore [0..1]: """ % { 'curdir': self.curdir}, + result.stdout) + self.assertEqual("", result.stderr) + self.assertEqual("contents", read_file(pj(self.curdir, "path/to/file2"))) + self.assertEqual(False, file_exists(pj(self.trash_dir, "info", "file2.trashinfo"))) + self.assertEqual(False, file_exists(pj(self.trash_dir, "files", "file2"))) + + def test_restore_with_relative_path(self): + self.fake_trash_dir.add_trashed_file( + "file1", pj(self.curdir, "path", "to", "file1"), "contents") + self.assertEqual(True, file_exists(pj(self.trash_dir, "info", "file1.trashinfo"))) + self.assertEqual(True, file_exists(pj(self.trash_dir, "files", "file1"))) + + result = self.run_command("trash-restore", + ["%(curdir)s" % {'curdir': "."}, + '--sort=path'], input='0') + + self.assertEqual("""\ + 0 2000-01-01 00:00:01 %(curdir)s/path/to/file1 +What file to restore [0..0]: """ % {'curdir': self.curdir}, + result.stdout) + self.assertEqual("", result.stderr) + self.assertEqual("contents", read_file(pj(self.curdir, "path/to/file1"))) + self.assertEqual(False, file_exists(pj(self.trash_dir, "info", "file1.trashinfo"))) + self.assertEqual(False, file_exists(pj(self.trash_dir, "files", "file1"))) + + def run_command(self, command, args=None, input=''): + if args is None: + args = [] + return run_command.run_command(self.curdir, + command, + ["--trash-dir", self.trash_dir] + args, + input) + + def tearDown(self): + self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_listing_in_restore_cmd.py trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_listing_in_restore_cmd.py --- trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_listing_in_restore_cmd.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_listing_in_restore_cmd.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -import unittest - -from mock import Mock - -from trashcli.restore.file_system import FakeRestoreFileSystem -from trashcli.fs import contents_of -from trashcli.restore import RestoreCmd, TrashedFiles, make_trash_directories - - -class TestListingInRestoreCmd(unittest.TestCase): - def setUp(self): - trash_directories = make_trash_directories() - self.logger = Mock(spec=[]) - trashed_files = TrashedFiles(self.logger, - trash_directories, - None, - contents_of) - self.cmd = RestoreCmd(None, None, - exit=None, - input=None, - trashed_files=trashed_files, - mount_points=lambda: [], - fs=FakeRestoreFileSystem("dir")) - self.cmd.handle_trashed_files = self.capture_trashed_files - self.trashed_files = Mock(spec=['all_trashed_files']) - self.cmd.trashed_files = self.trashed_files - - def test_with_no_args_and_files_in_trashcan(self): - self.trashed_files.all_trashed_files.return_value = [ - FakeTrashedFile('', 'dir/location'), - FakeTrashedFile('', 'dir/location'), - FakeTrashedFile('', 'anotherdir/location') - ] - - self.cmd.run(['trash-restore']) - - assert [ - 'dir/location' - , 'dir/location' - ] == self.original_locations - - def test_with_no_args_and_files_in_trashcan_2(self): - self.trashed_files.all_trashed_files.return_value = [ - FakeTrashedFile('', '/dir/location'), - FakeTrashedFile('', '/dir/location'), - FakeTrashedFile('', '/specific/path'), - ] - - self.cmd.run(['trash-restore', '/specific/path']) - - assert self.original_locations == ['/specific/path'] - - def test_with_with_path_prefix_bug(self): - self.trashed_files.all_trashed_files.return_value = [ - FakeTrashedFile('', '/prefix'), - FakeTrashedFile('', '/prefix-with-other'), - ] - - self.cmd.run(['trash-restore', '/prefix']) - - assert self.original_locations == ['/prefix'] - - def capture_trashed_files(self, arg, overwrite=False): - self.original_locations = [] - for trashed_file in arg: - self.original_locations.append(trashed_file.original_location) - - -class FakeTrashedFile(object): - def __init__(self, deletion_date, original_location): - self.deletion_date = deletion_date - self.original_location = original_location - - def __repr__(self): - return ('FakeTrashedFile(\'%s\', \'%s\')' % (self.deletion_date, - self.original_location)) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_restore_arg_parser.py trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_restore_arg_parser.py --- trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_restore_arg_parser.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_restore_arg_parser.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -import unittest - -from trashcli.restore.parse_restore_args import parse_restore_args -from trashcli.restore.parse_restore_args import Command - - -class Test_parse_args(unittest.TestCase): - def test_default_path(self): - args = parse_restore_args([''], "curdir") - - self.assertEqual((Command.RunRestore, - {'path': 'curdir', - 'sort': 'date', - 'trash_dir': None, - 'overwrite': False}), - args) - - def test_path_specified_relative_path(self): - args = parse_restore_args(['', 'path'], "curdir") - - self.assertEqual((Command.RunRestore, - {'path': 'curdir/path', - 'sort': 'date', - 'trash_dir': None, - 'overwrite': False}), - args) - - def test_path_specified_fullpath(self): - args = parse_restore_args(['', '/a/path'], "ignored") - - self.assertEqual((Command.RunRestore, - {'path': '/a/path', - 'sort': 'date', - 'trash_dir': None, - 'overwrite': False}), - args) - - def test_show_version(self): - args = parse_restore_args(['', '--version'], "ignored") - - self.assertEqual((Command.PrintVersion, None), args) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trash_restore_cmd.py trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trash_restore_cmd.py --- trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trash_restore_cmd.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trash_restore_cmd.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -import unittest - -from six import StringIO - -from mock import Mock, call - -from trashcli.fs import contents_of -from trashcli.restore import ( - RestoreCmd, - TrashDirectory, - TrashedFiles, - make_trash_directories, -) -from trashcli.restore.trashed_file import TrashedFile -from trashcli.restore.file_system import RestoreFileSystem - -class TestTrashRestoreCmd(unittest.TestCase): - def setUp(self): - self.stdout = StringIO() - self.stderr = StringIO() - trash_directories = make_trash_directories() - self.logger = Mock(spec=[]) - trashed_files = TrashedFiles(self.logger, - trash_directories, - TrashDirectory(), - contents_of) - self.fs = Mock(spec=RestoreFileSystem) - self.fs.getcwd_as_realpath = lambda: "cwd" - self.cmd = RestoreCmd(stdout=self.stdout, - stderr=self.stderr, - exit=self.capture_exit_status, - input=lambda x: self.user_reply, - version=None, - trashed_files=trashed_files, - mount_points=lambda: [], - fs=self.fs) - - def capture_exit_status(self, exit_status): - self.exit_status = exit_status - - def test_should_print_version(self): - self.cmd.version = '1.2.3' - self.cmd.run(['trash-restore', '--version']) - - assert 'trash-restore 1.2.3\n' == self.stdout.getvalue() - - def test_with_no_args_and_no_files_in_trashcan(self): - self.cmd.curdir = lambda: "cwd" - - self.cmd.run(['trash-restore']) - - assert ("No files trashed from current dir ('cwd')\n" == - self.stdout.getvalue()) - - def test_until_the_restore_unit(self): - self.fs.path_exists.return_value = False - trashed_file = TrashedFile( - 'parent/path', - None, - 'info_file', - 'orig_file') - - self.user_reply = '0' - self.cmd.restore_asking_the_user([trashed_file]) - - assert '' == self.stdout.getvalue() - assert '' == self.stderr.getvalue() - assert [call.path_exists('parent/path'), - call.mkdirs('parent'), - call.move('orig_file', 'parent/path'), - call.remove_file('info_file')] == self.fs.mock_calls - - def test_when_user_reply_with_empty_string(self): - self.user_reply = '' - - self.cmd.restore_asking_the_user([]) - - assert 'Exiting\n' == self.stdout.getvalue() - - def test_when_user_reply_with_not_number(self): - self.user_reply = 'non numeric' - - self.cmd.restore_asking_the_user([]) - - assert 'Invalid entry: not an index: non numeric\n' == \ - self.stderr.getvalue() - assert '' == self.stdout.getvalue() - assert 1 == self.exit_status diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trashed_file_restore_integration.py trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trashed_file_restore_integration.py --- trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trashed_file_restore_integration.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trashed_file_restore_integration.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -import os -import unittest - -from mock import Mock - -from trashcli.restore.file_system import FakeRestoreFileSystem -from trashcli.fs import contents_of -from trashcli.restore import ( - RestoreCmd, - TrashDirectory, - TrashedFiles, - make_trash_directories, -) -from trashcli.restore.trashed_file import TrashedFile - -from tests.support.files import make_empty_file -from tests.support.my_path import MyPath - - -class TestTrashedFileRestoreIntegration(unittest.TestCase): - def setUp(self): - self.temp_dir = MyPath.make_temp_dir() - trash_directories = make_trash_directories() - self.logger = Mock(spec=[]) - trashed_files = TrashedFiles(self.logger, - trash_directories, - TrashDirectory(), - contents_of) - self.cmd = RestoreCmd(None, - None, - exit=None, - input=None, - trashed_files=trashed_files, - mount_points=lambda: [], - fs=FakeRestoreFileSystem()) - - def test_restore(self): - trashed_file = TrashedFile(self.temp_dir / 'parent/path', - None, - self.temp_dir / 'info_file', - self.temp_dir / 'orig') - make_empty_file(self.temp_dir / 'orig') - make_empty_file(self.temp_dir / 'info_file') - - self.cmd.restore(trashed_file) - - assert os.path.exists(self.temp_dir / 'parent/path') - assert not os.path.exists(self.temp_dir / 'info_file') - assert not os.path.exists(self.temp_dir / 'orig') - - def test_restore_over_existing_file(self): - trashed_file = TrashedFile(self.temp_dir / 'path', None, None, None) - make_empty_file(self.temp_dir / 'path') - - self.assertRaises(IOError, lambda: self.cmd.restore(trashed_file)) - - def tearDown(self): - self.temp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trashed_files.py trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trashed_files.py --- trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trashed_files.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trashed_files.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -import datetime -import unittest - -from mock import Mock, call -from trashcli.restore import TrashedFiles - - -class TestTrashedFiles(unittest.TestCase): - def setUp(self): - self.trash_directories = Mock(spec=['trash_directories_or_user']) - self.trash_directory = Mock(spec=['all_info_files']) - self.contents_of = Mock() - self.logger = Mock(spec=[]) - self.trashed_files = TrashedFiles(self.logger, - self.trash_directories, - self.trash_directory, - self.contents_of) - - def test_something(self): - self.trash_directories.trash_directories_or_user.return_value = \ - [("path", "/volume")] - self.contents_of.return_value = 'Path=name\nDeletionDate=2001-01-01T10:10:10' - self.trash_directory.all_info_files.return_value = \ - [('trashinfo', 'info/info_path.trashinfo')] - - trashed_files = list(self.trashed_files.all_trashed_files([], None)) - - trashed_file = trashed_files[0] - assert '/volume/name' == trashed_file.original_location - assert (datetime.datetime(2001, 1, 1, 10, 10, 10) == - trashed_file.deletion_date) - assert 'info/info_path.trashinfo' == trashed_file.info_file - assert 'files/info_path' == trashed_file.original_file - assert ([call.trash_directories_or_user([], None)] == - self.trash_directories.mock_calls) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trashed_files_integration.py trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trashed_files_integration.py --- trash-cli-0.23.2.13.2/tests/test_restore/restore_cmd/test_trashed_files_integration.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/restore_cmd/test_trashed_files_integration.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -import datetime -import unittest - -from mock import Mock -from trashcli.fs import contents_of, remove_file -from trashcli.restore import TrashedFiles - -from tests.support.files import make_file, require_empty_dir -from tests.support.remove_dir_if_exists import remove_dir_if_exists - - -class TestTrashedFilesIntegration(unittest.TestCase): - def setUp(self): - self.trash_directories = Mock(spec=['trash_directories_or_user']) - self.trash_directory = Mock(spec=['all_info_files']) - self.logger = Mock(spec=[]) - self.trashed_files = TrashedFiles(self.logger, - self.trash_directories, - self.trash_directory, - contents_of) - - def test_something(self): - require_empty_dir('info') - self.trash_directories.trash_directories_or_user.return_value = \ - [("path", "/volume")] - make_file('info/info_path.trashinfo', - 'Path=name\nDeletionDate=2001-01-01T10:10:10') - self.trash_directory.all_info_files = Mock([], return_value=[ - ('trashinfo', 'info/info_path.trashinfo')]) - - trashed_files = list(self.trashed_files.all_trashed_files([], None)) - - trashed_file = trashed_files[0] - assert '/volume/name' == trashed_file.original_location - assert (datetime.datetime(2001, 1, 1, 10, 10, 10) == - trashed_file.deletion_date) - assert 'info/info_path.trashinfo' == trashed_file.info_file - assert 'files/info_path' == trashed_file.original_file - - def tearDown(self): - remove_file('info/info_path.trashinfo') - remove_dir_if_exists('info') diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/support/__init__.py trash-cli-0.23.11.10/tests/test_restore/support/__init__.py --- trash-cli-0.23.2.13.2/tests/test_restore/support/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/support/__init__.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,3 @@ + + + diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/support/a_trashed_file.py trash-cli-0.23.11.10/tests/test_restore/support/a_trashed_file.py --- trash-cli-0.23.2.13.2/tests/test_restore/support/a_trashed_file.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/support/a_trashed_file.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,15 @@ +from typing import NamedTuple + + +def a_trashed_file(trashed_from, info_file, backup_copy): + return ATrashedFile(trashed_from=str(trashed_from), + info_file=str(info_file), + backup_copy=str(backup_copy)) + + +class ATrashedFile(NamedTuple('ATrashedFile', [ + ('trashed_from', str), + ('info_file', str), + ('backup_copy', str), +])): + pass diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/support/fake_restore_fs.py trash-cli-0.23.11.10/tests/test_restore/support/fake_restore_fs.py --- trash-cli-0.23.2.13.2/tests/test_restore/support/fake_restore_fs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/support/fake_restore_fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,75 @@ +import os + +from tests.test_put.support.fake_fs.fake_fs import FakeFs +from tests.test_restore.support.a_trashed_file import ATrashedFile +from trashcli.fs import PathExists +from trashcli.fstab.volumes import Volumes, FakeVolumes +from trashcli.put.format_trash_info import format_trashinfo +from trashcli.restore.file_system import ListingFileSystem, FileReader, \ + RestoreWriteFileSystem, RestoreReadFileSystem + + +class FakeRestoreFs(ListingFileSystem, + Volumes, FileReader, RestoreWriteFileSystem, + RestoreReadFileSystem, PathExists): + + def exists(self, path): + return self.path_exists(path) + + def path_exists(self, path): + return self.fake_fs.exists(path) + + def __init__(self): + self.fake_fs = FakeFs() + self.mount_points = [] + + def mkdirs(self, path): + self.fake_fs.makedirs(path, 755) + + def move(self, path, dest): + self.fake_fs.move(path, dest) + + def remove_file(self, path): + self.fake_fs.remove_file(path) + + def add_volume(self, mount_point): + self.mount_points.append(mount_point) + + def list_mount_points(self): + return FakeVolumes(self.mount_points).list_mount_points() + + def volume_of(self, path): + return FakeVolumes(self.mount_points).volume_of(path) + + + def make_trashed_file(self, from_path, trash_dir, time, + original_file_content): + content = format_trashinfo(from_path, time) + basename = os.path.basename(from_path) + info_path = os.path.join(trash_dir, 'info', "%s.trashinfo" % basename) + backup_copy_path = os.path.join(trash_dir, 'files', basename) + trashed_file = ATrashedFile(trashed_from=from_path, + info_file=info_path, + backup_copy=backup_copy_path) + self.add_file(info_path, content) + self.add_file(backup_copy_path, original_file_content.encode('utf-8')) + return trashed_file + + def add_trash_file(self, from_path, trash_dir, time, original_file_content): + content = format_trashinfo(from_path, time) + basename = os.path.basename(from_path) + info_path = os.path.join(trash_dir, 'info', "%s.trashinfo" % basename) + backup_copy_path = os.path.join(trash_dir, 'files', basename) + self.add_file(info_path, content) + self.add_file(backup_copy_path, original_file_content.encode('utf-8')) + + def add_file(self, path, content=b''): + self.fake_fs.makedirs(os.path.dirname(path), 755) + self.fake_fs.make_file(path, content) + + def list_files_in_dir(self, dir_path): + for file_path in self.fake_fs.listdir(dir_path): + yield os.path.join(dir_path, file_path) + + def contents_of(self, path): + return self.fake_fs.read(path).decode('utf-8') diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/support/has_been_restored_matcher.py trash-cli-0.23.11.10/tests/test_restore/support/has_been_restored_matcher.py --- trash-cli-0.23.2.13.2/tests/test_restore/support/has_been_restored_matcher.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/support/has_been_restored_matcher.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,130 @@ +from typing import NamedTuple, Union + +from trashcli.fs import PathExists + + +def has_been_restored(fs): # type: (PathExists) -> HasBeenRestoredBaseMatcher + return HasBeenRestoredBaseMatcher(fs, HasBeenRestoredExpectations()) + + +def has_not_been_restored( + fs): # type: (PathExists) -> HasBeenRestoredBaseMatcher + return HasBeenRestoredBaseMatcher(fs, HasNotBeenYetRestoredExpectations()) + + +class ShouldExists(NamedTuple('ShouldExists', [ + ('name', str), ('path', str)])): + def expectation_as_text(self): + return "should exists" + + def should_exists(self): + return True + + def actual(self, actually_exists): + return {True: "and it does", + False: "but it does not"}[actually_exists] + + +class ShouldNotExists( + NamedTuple('ShouldNotExists', [('name', str), ('path', str)])): + def expectation_as_text(self): + return "should not exists" + + def should_exists(self): + return False + + def actual(self, actually_exists): + return {False: "and it does not", + True: "but it does"}[actually_exists] + + +class Satisfaction: + def __init__(self, expectation, actually_exists): + self.expectation = expectation + self.actually_exists = actually_exists + + def expectations_satisfied(self): + return self.actually_exists == self.expectation.should_exists() + + def actual_description(self): + return self.expectation.actual(self.actually_exists) + + def ok_or_fail_text(self): + return {True: "OK", False: "FAIL"}[ + self.expectations_satisfied()] + + def kind_of_file(self): + return self.expectation.name + + def satisfaction_description(self): + return "{0} {1} {2} {3}: '{4}'".format( + self.ok_or_fail_text(), + self.kind_of_file(), + self.expectation.expectation_as_text(), + self.actual_description(), + self.expectation.path + ) + + +class HasBeenRestoredExpectations: + def expectations_for_file(self, a_trashed_file): + return [ + ShouldExists("original_location", a_trashed_file.trashed_from), + ShouldNotExists("info_file", a_trashed_file.info_file), + ShouldNotExists("backup_copy", a_trashed_file.backup_copy), + ] + + +class HasNotBeenYetRestoredExpectations: + def expectations_for_file(self, a_trashed_file): + return [ + ShouldNotExists("original_location", a_trashed_file.trashed_from), + ShouldExists("info_file", a_trashed_file.info_file), + ShouldExists("backup_copy", a_trashed_file.backup_copy), + ] + + +Expectations = Union[HasBeenRestoredExpectations,HasNotBeenYetRestoredExpectations] + + +class HasBeenRestoredBaseMatcher: + def __init__(self, + fs, # type: PathExists + expectations_maker, # type: Expectations + ): + self.fs = fs + self.expectations_maker = expectations_maker + + def matches(self, a_trashed_file): + return len(self._expectations_failed(a_trashed_file)) == 0 + + def describe_mismatch(self, a_trashed_file, focus_on=None): + expectations_satisfactions = self._expectations_satisfactions( + a_trashed_file, + focus_on) + + return ("Expected file to be restore but it has not:\n" + + "".join(" - %s\n" % satisfaction.satisfaction_description() + for satisfaction in expectations_satisfactions)) + + def describe(self, description): + return "The file has been restored" + + def _expectations_failed(self, a_trashed_file): + return [ + satisfaction for satisfaction in + self._expectations_satisfactions(a_trashed_file, focus_on=None) + if not satisfaction.expectations_satisfied()] + + def _expectations_satisfactions(self, a_trashed_file, focus_on=None): + return [ + Satisfaction(e, self.fs.exists(e.path)) for e in + self._expectations_for(a_trashed_file, focus_on)] + + def _expectations_for(self, a_trashed_file, focus_on=None): + all_expectations = self.expectations_maker.expectations_for_file( + a_trashed_file) + if focus_on is None: + return all_expectations + else: + return [e for e in all_expectations if e.name == focus_on] diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/support/restore_file_fixture.py trash-cli-0.23.11.10/tests/test_restore/support/restore_file_fixture.py --- trash-cli-0.23.2.13.2/tests/test_restore/support/restore_file_fixture.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/support/restore_file_fixture.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,26 @@ +import os + +from tests.fake_trash_dir import trashinfo_content_default_date +from tests.support.files import make_file + + +class RestoreFileFixture: + def __init__(self, XDG_DATA_HOME): + self.XDG_DATA_HOME = XDG_DATA_HOME + + def having_a_trashed_file(self, path): + self.make_file('%s/info/foo.trashinfo' % self._trash_dir(), + trashinfo_content_default_date(path)) + self.make_file('%s/files/foo' % self._trash_dir()) + + def make_file(self, filename, contents=''): + return make_file(filename, contents) + + def make_empty_file(self, filename): + return self.make_file(filename) + + def _trash_dir(self): + return "%s/Trash" % self.XDG_DATA_HOME + + def file_should_have_been_restored(self, filename): + assert os.path.exists(filename) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/support/restore_user.py trash-cli-0.23.11.10/tests/test_restore/support/restore_user.py --- trash-cli-0.23.2.13.2/tests/test_restore/support/restore_user.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/support/restore_user.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,78 @@ +import sys +from io import StringIO +from typing import Dict + +from tests.run_command import CmdResult +from trashcli.fstab.volumes import Volumes +from trashcli.lib.my_input import HardCodedInput +from trashcli.restore.file_system import FakeReadCwd, FileReader, \ + RestoreReadFileSystem, \ + RestoreWriteFileSystem, ListingFileSystem +from trashcli.restore.info_dir_searcher import InfoDirSearcher +from trashcli.restore.info_files import InfoFiles +from trashcli.restore.restore_cmd import RestoreCmd +from trashcli.restore.trash_directories import TrashDirectoriesImpl +from trashcli.restore.trashed_files import TrashedFiles + + +class MemoLogger: + def __init__(self): + self.messages = [] + + def warning(self, msg): + self.messages.append("warning: " + msg) + + +class RestoreUser: + def __init__(self, + environ, # type: Dict[str, str] + uid, # type: int + file_reader, # type: FileReader + read_fs, # type: RestoreReadFileSystem + write_fs, # type: RestoreWriteFileSystem + listing_file_system, # type: ListingFileSystem + version, # type: str + volumes, # type: Volumes + ): + self.environ = environ + self.uid = uid + self.file_reader = file_reader + self.read_fs = read_fs + self.write_fs = write_fs + self.listing_file_system = listing_file_system + self.version = version + self.volumes = volumes + + no_args = object() + + def run_restore(self, args=no_args, reply='', from_dir=None): + args = [] if args is self.no_args else args + stdout = StringIO() + stderr = StringIO() + read_cwd = FakeReadCwd(from_dir) + logger = MemoLogger() + trash_directories = TrashDirectoriesImpl(self.volumes, + self.uid, + self.environ) + searcher = InfoDirSearcher(trash_directories, + InfoFiles(self.listing_file_system)) + trashed_files = TrashedFiles(logger, + self.file_reader, + searcher) + cmd = RestoreCmd.make(stdout=stdout, + stderr=stderr, + exit=sys.exit, + input=HardCodedInput(reply), + version=self.version, + trashed_files=trashed_files, + read_fs=self.read_fs, + write_fs=self.write_fs, + read_cwd=read_cwd) + + try: + exit_code = cmd.run(args) + except SystemExit as e: + exit_code = e.code + + return CmdResult(stdout.getvalue(), + stderr.getvalue(), exit_code) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_all_trash_directories.py trash-cli-0.23.11.10/tests/test_restore/test_all_trash_directories.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_all_trash_directories.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_all_trash_directories.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -import unittest - -from mock import Mock -from trashcli.restore.trash_directories import TrashDirectories - - -class TestTrashDirectories(unittest.TestCase): - def setUp(self): - volume_of = lambda x: "volume_of(%s)" % x - environ = {'HOME': '~'} - self.trash_directories = TrashDirectories(volume_of, 123, environ) - - def test_list_all_directories(self): - result = list(self.trash_directories.all_trash_directories( - ['/', '/mnt'] - )) - - assert ([ - ('~/.local/share/Trash', 'volume_of(~/.local/share/Trash)'), - ('/.Trash/123', '/'), - ('/.Trash-123', '/'), - ('/mnt/.Trash/123', '/mnt'), - ('/mnt/.Trash-123', '/mnt')] == - result) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_end_to_end_restore.py trash-cli-0.23.11.10/tests/test_restore/test_end_to_end_restore.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_end_to_end_restore.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_end_to_end_restore.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,91 +0,0 @@ -import os -import unittest -from datetime import datetime -from os.path import exists as file_exists, join as pj - -import pytest -from trashcli.fs import read_file - -from .. import run_command -from ..fake_trash_dir import FakeTrashDir -from ..support.my_path import MyPath - - -@pytest.mark.slow -class TestEndToEndRestore(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - self.curdir = self.tmp_dir / "cwd" - self.trash_dir = self.tmp_dir / "trash-dir" - os.makedirs(self.curdir) - self.fake_trash_dir = FakeTrashDir(self.trash_dir) - - def test_no_file_trashed(self): - result = self.run_command("trash-restore") - - self.assertEqual("""\ -No files trashed from current dir ('%s') -""" % self.curdir, result.stdout) - - def test_original_file_not_existing(self): - self.fake_trash_dir.add_trashinfo3("foo", "/path", datetime(2000,1,1,0,0,1)) - - result = self.run_command("trash-restore", ["/"], input='0') - - self.assertEqual("""\ - 0 2000-01-01 00:00:01 /path -What file to restore [0..0]: """, - result.stdout) - self.assertEqual("[Errno 2] No such file or directory: '%s/files/foo'\n" % - self.trash_dir, - result.stderr) - - def test_restore_happy_path(self): - self.fake_trash_dir.add_trashed_file( - "file1", pj(self.curdir, "path", "to", "file1"), "contents") - self.fake_trash_dir.add_trashed_file( - "file2", pj(self.curdir, "path", "to", "file2"), "contents") - self.assertEqual(True, file_exists(pj(self.trash_dir, "info", "file2.trashinfo"))) - self.assertEqual(True, file_exists(pj(self.trash_dir, "files", "file2"))) - - result = self.run_command("trash-restore", ["/", '--sort=path'], input='1') - - self.assertEqual("""\ - 0 2000-01-01 00:00:01 %(curdir)s/path/to/file1 - 1 2000-01-01 00:00:01 %(curdir)s/path/to/file2 -What file to restore [0..1]: """ % { 'curdir': self.curdir}, - result.stdout) - self.assertEqual("", result.stderr) - self.assertEqual("contents", read_file(pj(self.curdir, "path/to/file2"))) - self.assertEqual(False, file_exists(pj(self.trash_dir, "info", "file2.trashinfo"))) - self.assertEqual(False, file_exists(pj(self.trash_dir, "files", "file2"))) - - def test_restore_with_relative_path(self): - self.fake_trash_dir.add_trashed_file( - "file1", pj(self.curdir, "path", "to", "file1"), "contents") - self.assertEqual(True, file_exists(pj(self.trash_dir, "info", "file1.trashinfo"))) - self.assertEqual(True, file_exists(pj(self.trash_dir, "files", "file1"))) - - result = self.run_command("trash-restore", - ["%(curdir)s" % {'curdir': "."}, - '--sort=path'], input='0') - - self.assertEqual("""\ - 0 2000-01-01 00:00:01 %(curdir)s/path/to/file1 -What file to restore [0..0]: """ % {'curdir': self.curdir}, - result.stdout) - self.assertEqual("", result.stderr) - self.assertEqual("contents", read_file(pj(self.curdir, "path/to/file1"))) - self.assertEqual(False, file_exists(pj(self.trash_dir, "info", "file1.trashinfo"))) - self.assertEqual(False, file_exists(pj(self.trash_dir, "files", "file1"))) - - def run_command(self, command, args=None, input=''): - if args is None: - args = [] - return run_command.run_command(self.curdir, - command, - ["--trash-dir", self.trash_dir] + args, - input) - - def tearDown(self): - self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_is_trashed_from_path.py trash-cli-0.23.11.10/tests/test_restore/test_is_trashed_from_path.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_is_trashed_from_path.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_is_trashed_from_path.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -import unittest - -from trashcli.restore.restore_cmd import original_location_matches_path - - -class Test_original_location_matches_path(unittest.TestCase): - def test1(self): - assert original_location_matches_path("/full/path", "/full") == True - - def test2(self): - assert original_location_matches_path("/full/path", "/full/path") == True - - def test3(self): - assert original_location_matches_path("/prefix-extension", "/prefix") == False - - def test_root(self): - assert original_location_matches_path("/any/path", "/") == True diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_parse_indexes.py trash-cli-0.23.11.10/tests/test_restore/test_parse_indexes.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_parse_indexes.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_parse_indexes.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,53 +0,0 @@ -import unittest - -import six - -from trashcli.restore.range import Range -from trashcli.restore.sequences import Sequences -from trashcli.restore.single import Single -from trashcli.restore.restore_asking_the_user import InvalidEntry, parse_indexes - - -class Test_parse_indexes(unittest.TestCase): - def test_non_numeric(self): - with six.assertRaisesRegex(self, InvalidEntry, "^not an index: a$"): - parse_indexes("a", 10) - - def test(self): - with six.assertRaisesRegex(self, InvalidEntry, "^out of range 0..9: 10$"): - parse_indexes("10", 10) - - def test2(self): - self.assertEqual(Sequences([Single(9)]), parse_indexes("9", 10)) - - def test3(self): - self.assertEqual(Sequences([Single(0)]), parse_indexes("0", 10)) - - def test4(self): - assert Sequences([Range(1, 4)]) == parse_indexes("1-4", 10) - - def test5(self): - self.assertEqual(Sequences([Single(1), - Single(2), - Single(3), - Single(4)]), - parse_indexes("1,2,3,4", 10)) - - def test_interval_without_start(self): - with six.assertRaisesRegex(self, InvalidEntry, "^open interval: -1$"): - parse_indexes("-1", 10) - - def test_interval_without_end(self): - with six.assertRaisesRegex(self, InvalidEntry, "^open interval: 1-$"): - parse_indexes("1-", 10) - - def test_complex(self): - indexes = parse_indexes("1-5,7", 10) - self.assertEqual(Sequences([Range(1, 5), Single(7)]), indexes) - - -class TestSequences(unittest.TestCase): - def test(self): - sequences = parse_indexes("1-5,7", 10) - result = [index for index in sequences.all_indexes()] - self.assertEqual([1, 2, 3, 4, 5, 7], result) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_restore_asking_the_user.py trash-cli-0.23.11.10/tests/test_restore/test_restore_asking_the_user.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_restore_asking_the_user.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_restore_asking_the_user.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -import unittest - -from mock import Mock, call -from trashcli.restore.restore_asking_the_user import RestoreAskingTheUser - - -class TestRestoreAskingTheUser(unittest.TestCase): - def setUp(self): - self.input = Mock(spec=['']) - self.println = Mock(spec=['']) - self.restore = Mock(spec=['']) - self.die = Mock(spec=['']) - self.restorer = RestoreAskingTheUser(self.input, - self.println, - self.restore, - self.die) - - def test(self): - self.input.return_value = '0' - - self.restorer.restore_asking_the_user(['trashed_file1', - 'trashed_file2']) - - self.assertEqual([call('What file to restore [0..1]: ')], - self.input.mock_calls) - self.assertEqual([], self.println.mock_calls) - self.assertEqual([call('trashed_file1', False)] , - self.restore.mock_calls) - self.assertEqual([], self.die.mock_calls) - - def test2(self): - self.input.side_effect = KeyboardInterrupt - - self.restorer.restore_asking_the_user(['trashed_file1', - 'trashed_file2']) - - self.assertEqual([call('What file to restore [0..1]: ')], - self.input.mock_calls) - self.assertEqual([], self.println.mock_calls) - self.assertEqual([], self.restore.mock_calls) - self.assertEqual([call('')], self.die.mock_calls) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_restore_trash.py trash-cli-0.23.11.10/tests/test_restore/test_restore_trash.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_restore_trash.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_restore_trash.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,151 +0,0 @@ -import os -import unittest - -from six import StringIO - -import pytest -from mock import Mock - -from trashcli.fs import contents_of, remove_file -from trashcli.fstab import volume_of -from trashcli.restore import ( - RestoreCmd, - TrashDirectory, - TrashedFiles, -) -from trashcli.restore.trash_directories import TrashDirectories2, \ - TrashDirectories - -from ..fake_trash_dir import trashinfo_content_default_date -from ..support.files import make_file, require_empty_dir -from ..support.my_path import MyPath -from trashcli.restore.file_system import FakeRestoreFileSystem - - -@pytest.mark.slow -class TestRestoreTrash(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - require_empty_dir(self.tmp_dir / 'XDG_DATA_HOME') - self.cwd = self.tmp_dir / "cwd" - self.user = RestoreTrashUser(self.tmp_dir / 'XDG_DATA_HOME', self.cwd) - - def test_it_does_nothing_when_no_file_have_been_found_in_current_dir(self): - self.user.chdir('/') - - self.user.run_restore() - - self.assertEqual("No files trashed from current dir ('/')\n", - self.user.stdout()) - - def test_gives_an_error_on_not_a_number_input(self): - self.user.having_a_trashed_file('/foo/bar') - self.user.chdir('/foo') - - self.user.run_restore(with_user_typing='+@notanumber') - - self.assertEqual('Invalid entry: not an index: +@notanumber\n', - self.user.stderr()) - - def test_it_gives_error_when_user_input_is_too_small(self): - self.user.having_a_trashed_file('/foo/bar') - self.user.chdir('/foo') - - self.user.run_restore(with_user_typing='1') - - self.assertEqual('Invalid entry: out of range 0..0: 1\n', - self.user.stderr()) - - def test_it_gives_error_when_user_input_is_too_large(self): - self.user.having_a_trashed_file('/foo/bar') - self.user.chdir('/foo') - - self.user.run_restore(with_user_typing='1') - - self.assertEqual('Invalid entry: out of range 0..0: 1\n', - self.user.stderr()) - - def test_it_shows_the_file_deleted_from_the_current_dir(self): - self.user.having_a_trashed_file('/foo/bar') - self.user.chdir('/foo') - - self.user.run_restore(with_user_typing='') - - self.assertEqual(' 0 2000-01-01 00:00:01 /foo/bar\n' - 'Exiting\n', self.user.stdout()) - self.assertEqual('', self.user.stderr()) - - def test_it_restores_the_file_selected_by_the_user(self): - self.user.having_a_file_trashed_from_current_dir('foo') - self.user.chdir(self.cwd) - - self.user.run_restore(with_user_typing='0') - - self.file_should_have_been_restored(self.cwd / 'foo') - - def test_it_refuses_overwriting_existing_file(self): - self.user.having_a_file_trashed_from_current_dir('foo') - self.user.chdir(self.cwd) - make_file(self.cwd / "foo") - - self.user.run_restore(with_user_typing='0') - - self.assertEqual('Refusing to overwrite existing file "foo".\n', - self.user.stderr()) - - def file_should_have_been_restored(self, filename): - assert os.path.exists(filename) - - def tearDown(self): - self.tmp_dir.clean_up() - - -class RestoreTrashUser: - def __init__(self, XDG_DATA_HOME, cwd): - self.XDG_DATA_HOME = XDG_DATA_HOME - self.out = StringIO() - self.err = StringIO() - self.cwd = cwd - self.fs = FakeRestoreFileSystem() - - def chdir(self, dir): - self.current_dir = dir - self.fs.chdir(dir) - - def run_restore(self, with_user_typing=''): - environ = {'XDG_DATA_HOME': self.XDG_DATA_HOME} - trash_directories = TrashDirectories(volume_of, os.getuid(), environ) - trash_directories2 = TrashDirectories2(volume_of, trash_directories) - logger = Mock(spec=[]) - trashed_files = TrashedFiles(logger, - trash_directories2, - TrashDirectory(), - contents_of) - RestoreCmd( - stdout=self.out, - stderr=self.err, - exit=[].append, - input=lambda msg: with_user_typing, - trashed_files=trashed_files, - mount_points=lambda: [], - fs=self.fs, - ).run([]) - - def having_a_file_trashed_from_current_dir(self, filename): - self.having_a_trashed_file(os.path.join(self.cwd, filename)) - remove_file(filename) - assert not os.path.exists(filename) - - def having_a_trashed_file(self, path): - make_file('%s/info/foo.trashinfo' % self._trash_dir(), - trashinfo_content_default_date(path)) - make_file('%s/files/foo' % self._trash_dir()) - - def _trash_dir(self): - return "%s/Trash" % self.XDG_DATA_HOME - - def stdout(self): - return self.out.getvalue() - - def stderr(self): - return self.err.getvalue() diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_support/test_has_been_restored.py trash-cli-0.23.11.10/tests/test_restore/test_support/test_has_been_restored.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_support/test_has_been_restored.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_support/test_has_been_restored.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,86 @@ +import unittest + +from tests.test_put.support.fake_fs.fake_fs import FakeFs +from tests.test_restore.support.a_trashed_file import a_trashed_file +from tests.test_restore.support.has_been_restored_matcher import \ + has_been_restored + + +class TestHasBeenRestored(unittest.TestCase): + def setUp(self): + self.fs = FakeFs() + self.trashed_file = a_trashed_file(trashed_from='/original_location', + info_file='/info_path.trashinfo', + backup_copy='/backup_copy') + + def test_fail_if_original_location_does_not_exists(self): + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file, + focus_on='original_location') + assert result == ( + "Expected file to be restore but it has not:\n" + " - FAIL original_location should exists but it does not: '/original_location'\n" + ) + + def test_ok_if_original_location_does_not_exists(self): + self.fs.make_file('/original_location') + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file, + focus_on='original_location') + assert result == ( + "Expected file to be restore but it has not:\n" + " - OK original_location should exists and it does: '/original_location'\n" + ) + + def test_fail_if_info_file_exists(self): + self.fs.make_file('/info_path.trashinfo') + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file, + focus_on='info_file') + assert result == ( + "Expected file to be restore but it has not:\n" + " - FAIL info_file should not exists but it does: '/info_path.trashinfo'\n" + ) + + def test_ok_if_info_file_does_not_exists(self): + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file, + focus_on='info_file') + assert result == ( + "Expected file to be restore but it has not:\n" + " - OK info_file should not exists and it does not: '/info_path.trashinfo'\n" + ) + + def test_fail_if_backup_copy_exists(self): + self.fs.make_file('/backup_copy') + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file, + focus_on='backup_copy') + assert result == ( + "Expected file to be restore but it has not:\n" + " - FAIL backup_copy should not exists but it does: '/backup_copy'\n" + ) + + def test_ok_if_backup_copy_does_not_exists(self): + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file, + focus_on='backup_copy') + assert result == ( + "Expected file to be restore but it has not:\n" + " - OK backup_copy should not exists and it does not: '/backup_copy'\n" + ) + + def test_fail_if_not_yet_restored(self): + self.fs.make_file('/info_path.trashinfo') + self.fs.make_file('/backup_copy') + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file) + assert result == ( + "Expected file to be restore but it has not:\n" + " - FAIL original_location should exists but it does not: '/original_location'\n" + " - FAIL info_file should not exists but it does: '/info_path.trashinfo'\n" + " - FAIL backup_copy should not exists but it does: '/backup_copy'\n" + ) + + def test_ok_if_restored(self): + self.fs.make_file('/original_location') + result = has_been_restored(self.fs).describe_mismatch(self.trashed_file) + assert result == ( + "Expected file to be restore but it has not:\n" + " - OK original_location should exists and it does: '/original_location'\n" + " - OK info_file should not exists and it does not: '/info_path.trashinfo'\n" + " - OK backup_copy should not exists and it does not: '/backup_copy'\n" + ) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_trash_directories2.py trash-cli-0.23.11.10/tests/test_restore/test_trash_directories2.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_trash_directories2.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_trash_directories2.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -import unittest - -import pytest -from mock import Mock, call -from trashcli.restore.trash_directories import TrashDirectories2 - - -@pytest.mark.slow -class TestTrashDirectories2(unittest.TestCase): - def setUp(self): - self.trash_directories = Mock(spec=['all_trash_directories']) - self.volume_of = lambda x: "volume_of(%s)" % x - self.trash_directories2 = TrashDirectories2(self.volume_of, - self.trash_directories) - - def test_when_user_dir_is_none(self): - self.trash_directories.all_trash_directories.return_value = \ - "os-trash-directories" - - result = self.trash_directories2.trash_directories_or_user('volumes', - None) - - self.assertEqual([call.all_trash_directories('volumes')], - self.trash_directories.mock_calls) - self.assertEqual('os-trash-directories', result) - - def test_when_user_dir_is_specified(self): - self.trash_directories.all_trash_directories.return_value = \ - "os-trash-directories" - - result = self.trash_directories2.trash_directories_or_user( - 'volumes', 'user-trash_dir') - - self.assertEqual([], self.trash_directories.mock_calls) - self.assertEqual([('user-trash_dir', 'volume_of(user-trash_dir)')], - result) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/test_trash_directory.py trash-cli-0.23.11.10/tests/test_restore/test_trash_directory.py --- trash-cli-0.23.2.13.2/tests/test_restore/test_trash_directory.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/test_trash_directory.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -import unittest - -import six - -import pytest -from mock import Mock -from trashcli.restore import TrashDirectory - -from ..support.files import make_file, require_empty_dir -from ..support.my_path import MyPath - - -@pytest.mark.slow -class TestTrashDirectory(unittest.TestCase): - def setUp(self): - self.temp_dir = MyPath.make_temp_dir() - require_empty_dir(self.temp_dir / 'trash-dir') - self.trash_dir = TrashDirectory() - self.logger = Mock() - self.trash_dir.logger = self.logger - - def test_should_list_a_trashinfo(self): - make_file(self.temp_dir / 'trash-dir/info/foo.trashinfo') - - result = self.list_trashinfos() - - assert [('trashinfo', self.temp_dir / 'trash-dir/info/foo.trashinfo')] == result - - def test_should_list_multiple_trashinfo(self): - make_file(self.temp_dir / 'trash-dir/info/foo.trashinfo') - make_file(self.temp_dir / 'trash-dir/info/bar.trashinfo') - make_file(self.temp_dir / 'trash-dir/info/baz.trashinfo') - - result = self.list_trashinfos() - - six.assertCountEqual(self, - [('trashinfo', self.temp_dir / 'trash-dir/info/foo.trashinfo'), - ('trashinfo', self.temp_dir / 'trash-dir/info/baz.trashinfo'), - ('trashinfo', self.temp_dir / 'trash-dir/info/bar.trashinfo')], - result) - - def test_non_trashinfo_should_reported_as_a_warn(self): - make_file(self.temp_dir / 'trash-dir/info/not-a-trashinfo') - - result = self.list_trashinfos() - - six.assertCountEqual(self, - [('non_trashinfo', - self.temp_dir / 'trash-dir/info/not-a-trashinfo')], - result) - - def list_trashinfos(self): - return list(self.trash_dir.all_info_files(self.temp_dir / 'trash-dir')) - - def tearDown(self): - self.temp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/trashed_files/test_trashed_files.py trash-cli-0.23.11.10/tests/test_restore/trashed_files/test_trashed_files.py --- trash-cli-0.23.2.13.2/tests/test_restore/trashed_files/test_trashed_files.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/trashed_files/test_trashed_files.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,34 @@ +import datetime +import unittest + +from mock import Mock + +from trashcli.restore.file_system import FakeFileReader +from trashcli.restore.info_dir_searcher import InfoDirSearcher, FileFound +from trashcli.restore.trashed_files import TrashedFiles + + +class TestTrashedFiles(unittest.TestCase): + def setUp(self): + self.file_reader = FakeFileReader() + self.logger = Mock(spec=[]) + self.searcher = Mock(spec=InfoDirSearcher) + self.trashed_files = TrashedFiles(self.logger, + self.file_reader, + self.searcher) + + def test(self): + self.searcher.all_file_in_info_dir.return_value = [ + FileFound('trashinfo', 'info/info_path.trashinfo', '/volume') + ] + self.file_reader.set_content( + 'Path=name\nDeletionDate=2001-01-01T10:10:10') + + trashed_files = list(self.trashed_files.all_trashed_files(None)) + + trashed_file = trashed_files[0] + assert '/volume/name' == trashed_file.original_location + assert (datetime.datetime(2001, 1, 1, 10, 10, 10) == + trashed_file.deletion_date) + assert 'info/info_path.trashinfo' == trashed_file.info_file + assert 'files/info_path' == trashed_file.original_file diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/trashed_files/test_trashed_files_integration.py trash-cli-0.23.11.10/tests/test_restore/trashed_files/test_trashed_files_integration.py --- trash-cli-0.23.2.13.2/tests/test_restore/trashed_files/test_trashed_files_integration.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/trashed_files/test_trashed_files_integration.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,41 @@ +import datetime +import unittest + +from mock import Mock + +from tests.support.files import make_file, require_empty_dir +from tests.support.remove_dir_if_exists import remove_dir_if_exists +from trashcli.fs import remove_file +from trashcli.restore.file_system import RealFileReader +from trashcli.restore.info_dir_searcher import InfoDirSearcher, FileFound +from trashcli.restore.trashed_files import TrashedFiles + + +class TestTrashedFilesIntegration(unittest.TestCase): + def setUp(self): + self.logger = Mock(spec=[]) + self.searcher = Mock(spec=InfoDirSearcher) + self.trashed_files = TrashedFiles(self.logger, + RealFileReader(), + self.searcher) + + def test(self): + require_empty_dir('info') + self.searcher.all_file_in_info_dir.return_value = [ + FileFound('trashinfo', 'info/info_path.trashinfo', '/volume') + ] + make_file('info/info_path.trashinfo', + 'Path=name\nDeletionDate=2001-01-01T10:10:10') + + trashed_files = list(self.trashed_files.all_trashed_files(None)) + + trashed_file = trashed_files[0] + assert '/volume/name' == trashed_file.original_location + assert (datetime.datetime(2001, 1, 1, 10, 10, 10) == + trashed_file.deletion_date) + assert 'info/info_path.trashinfo' == trashed_file.info_file + assert 'files/info_path' == trashed_file.original_file + + def tearDown(self): + remove_file('info/info_path.trashinfo') + remove_dir_if_exists('info') diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_cmd/test_listing_in_restore_cmd.py trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_cmd/test_listing_in_restore_cmd.py --- trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_cmd/test_listing_in_restore_cmd.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_cmd/test_listing_in_restore_cmd.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,75 @@ +import unittest + +from mock import Mock +from six import StringIO + +from trashcli.restore.file_system import FakeReadCwd +from trashcli.restore.handler import Handler +from trashcli.restore.restore_cmd import RestoreCmd +from trashcli.restore.trashed_file import TrashedFile +from trashcli.restore.trashed_files import TrashedFiles + + +class TestListingInRestoreCmd(unittest.TestCase): + def setUp(self): + self.logger = Mock(spec=[]) + self.trashed_files = Mock(spec=TrashedFiles) + self.trashed_files.all_trashed_files = Mock() + self.original_locations = [] + self.fake_handler = FakeHandler(self.original_locations) + self.cmd = RestoreCmd(stdout=StringIO(), + version="0.0.0", + trashed_files=self.trashed_files, + read_cwd=FakeReadCwd("dir"), + handler=self.fake_handler) + + def test_with_no_args_and_files_in_trashcan(self): + self.trashed_files.all_trashed_files.return_value = [ + a_trashed_file('dir/location'), + a_trashed_file('dir/location'), + a_trashed_file('anotherdir/location') + ] + + self.cmd.run(['trash-restore']) + + assert [ + 'dir/location', + 'dir/location' + ] == self.original_locations + + def test_with_no_args_and_files_in_trashcan_2(self): + self.trashed_files.all_trashed_files.return_value = [ + a_trashed_file('/dir/location'), + a_trashed_file('/dir/location'), + a_trashed_file('/specific/path'), + ] + + self.cmd.run(['trash-restore', '/specific/path']) + + assert self.original_locations == ['/specific/path'] + + def test_with_with_path_prefix_bug(self): + self.trashed_files.all_trashed_files.return_value = [ + a_trashed_file('/prefix'), + a_trashed_file('/prefix-with-other'), + ] + + self.cmd.run(['trash-restore', '/prefix']) + + assert self.original_locations == ['/prefix'] + + +def a_trashed_file(original_location): + return TrashedFile(original_location=original_location, + deletion_date="a date", + info_file="", + original_file="") + + +class FakeHandler(Handler): + def __init__(self, original_locations): + self.original_locations = original_locations + + def handle_trashed_files(self, trashed_files, _overwrite): + for trashed_file in trashed_files: + self.original_locations.append(trashed_file.original_location) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_cmd/test_trashed_file_restore_integration.py trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_cmd/test_trashed_file_restore_integration.py --- trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_cmd/test_trashed_file_restore_integration.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_cmd/test_trashed_file_restore_integration.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,63 @@ +import os +import unittest + +from mock import Mock +from six import StringIO + +from tests.support.files import make_empty_file +from tests.support.my_path import MyPath +from trashcli.lib.my_input import HardCodedInput +from trashcli.restore.file_system import RealRestoreWriteFileSystem, \ + FakeReadCwd, RealRestoreReadFileSystem +from trashcli.restore.restore_cmd import RestoreCmd +from trashcli.restore.trashed_file import TrashedFile +from trashcli.restore.trashed_files import TrashedFiles + + +class TestTrashedFileRestoreIntegration(unittest.TestCase): + def setUp(self): + self.stdout = StringIO() + self.stderr = StringIO() + self.input = HardCodedInput() + self.temp_dir = MyPath.make_temp_dir() + cwd = self.temp_dir + self.logger = Mock(spec=[]) + self.trashed_files = Mock(spec=TrashedFiles) + self.cmd = RestoreCmd.make(stdout=self.stdout, + stderr=self.stderr, + exit=lambda _: None, + input=self.input, + version="0.0.0", + trashed_files=self.trashed_files, + read_fs=RealRestoreReadFileSystem(), + write_fs=RealRestoreWriteFileSystem(), + read_cwd=FakeReadCwd(cwd)) + + def test_restore(self): + trashed_file = TrashedFile(self.temp_dir / 'parent/path', + None, + self.temp_dir / 'info_file', + self.temp_dir / 'orig') + make_empty_file(self.temp_dir / 'orig') + make_empty_file(self.temp_dir / 'info_file') + self.input.set_reply('0') + + self.trashed_files.all_trashed_files.return_value = [trashed_file] + self.cmd.run(['trash-restore']) + + assert os.path.exists(self.temp_dir / 'parent/path') + assert not os.path.exists(self.temp_dir / 'info_file') + assert not os.path.exists(self.temp_dir / 'orig') + + def test_restore_over_existing_file(self): + trashed_file = TrashedFile(self.temp_dir / 'path', None, None, None) + make_empty_file(self.temp_dir / 'path') + self.input.set_reply('0') + + self.trashed_files.all_trashed_files.return_value = [trashed_file] + self.cmd.run(['trash-restore']) + + assert self.stderr.getvalue() == 'Refusing to overwrite existing file "path".\n' + + def tearDown(self): + self.temp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_user/test_restore.py trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_user/test_restore.py --- trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_user/test_restore.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_user/test_restore.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,95 @@ +import datetime + +from tests.support.asserts.assert_that import assert_that +from tests.test_restore.support.fake_restore_fs import FakeRestoreFs +from tests.test_restore.support.has_been_restored_matcher import \ + has_been_restored, has_not_been_restored +from tests.test_restore.support.restore_user import RestoreUser + + +class TestSearcher: + def setup_method(self): + self.fs = FakeRestoreFs() + self.user = RestoreUser(environ={'HOME': '/home/user'}, + uid=123, + file_reader=self.fs, + read_fs=self.fs, + write_fs=self.fs, + listing_file_system=self.fs, + version='1.0', + volumes=self.fs) + + def test_will_not_detect_trashed_file_in_dirs_other_than_cur_dir(self): + self.fs.add_volume('/disk1') + self.fs.add_file('/disk1/.Trash-123/info/not_a_trashinfo') + self.fs.add_trash_file("/foo", '/home/user/.local/share/Trash', + date_at(2018, 1, 1), '') + self.fs.add_trash_file("/disk1/bar", '/disk1/.Trash-123', + date_at(2018, 1, 1), '') + + res = self.run_restore([], from_dir='/home/user') + + assert (res.output() == + "No files trashed from current dir ('/home/user')\n") + + def test_will_show_file_in_cur_dir(self): + self.fs.add_trash_file("/home/user/foo", + '/home/user/.local/share/Trash', + date_at(2018, 1, 1), '') + + res = self.run_restore([], from_dir='/home/user') + + assert (res.output() == + ' 0 2018-01-01 00:00:00 /home/user/foo\n' + 'Exiting\n') + + def test_actual_restore(self): + trashed_file = self.fs.make_trashed_file("/home/user/foo", + '/home/user/.local/share/Trash', + date_at(2018, 1, 1), + "contents of foo\n") + assert_that(trashed_file, has_not_been_restored(self.fs)) + + res = self.run_restore([], reply="0", from_dir='/home/user') + + assert (res.output() == + ' 0 2018-01-01 00:00:00 /home/user/foo\n') + assert_that(trashed_file, has_been_restored(self.fs)) + assert (self.fs.contents_of('/home/user/foo') == "contents of foo\n") + + def test_will_sort_by_date_by_default(self): + self.add_file_trashed_at("/home/user/third", date_at(2013, 1, 1)) + self.add_file_trashed_at("/home/user/second", date_at(2012, 1, 1)) + self.add_file_trashed_at("/home/user/first", date_at(2011, 1, 1)) + + res = self.run_restore([], from_dir='/home/user') + + assert (res.output() == + ' 0 2011-01-01 00:00:00 /home/user/first\n' + ' 1 2012-01-01 00:00:00 /home/user/second\n' + ' 2 2013-01-01 00:00:00 /home/user/third\n' + 'Exiting\n') + + def test_will_sort_by_path(self): + self.add_file_trashed_at("/home/user/ccc", date_at(2011, 1, 1)) + self.add_file_trashed_at("/home/user/bbb", date_at(2011, 1, 1)) + self.add_file_trashed_at("/home/user/aaa", date_at(2011, 1, 1)) + + res = self.run_restore(['trash-restore', '--sort=path'], from_dir='/home/user') + + assert (res.output() == + ' 0 2011-01-01 00:00:00 /home/user/aaa\n' + ' 1 2011-01-01 00:00:00 /home/user/bbb\n' + ' 2 2011-01-01 00:00:00 /home/user/ccc\n' + 'Exiting\n') + + def run_restore(self, args, reply='', from_dir=None): + return self.user.run_restore(args, reply, from_dir) + + def add_file_trashed_at(self, original_location, deletion_date): + self.fs.make_trashed_file(original_location, '/home/user/.local/share/Trash', + deletion_date, '') + + +def date_at(year, month, day): + return datetime.datetime(year, month, day, 0, 0) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_user/test_restore2.py trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_user/test_restore2.py --- trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_user/test_restore2.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_user/test_restore2.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,79 @@ +import datetime +import unittest + +from mock import Mock, call + +from tests.test_restore.support.fake_restore_fs import FakeRestoreFs +from tests.test_restore.support.restore_user import RestoreUser +from trashcli.restore.file_system import RestoreWriteFileSystem + + +class TestRestore2(unittest.TestCase): + def setUp(self): + self.write_fs = Mock(spec=RestoreWriteFileSystem) + self.fs = FakeRestoreFs() + self.user = RestoreUser( + environ={'XDG_DATA_HOME': '/data_home'}, + uid=1000, + file_reader=self.fs, + read_fs=self.fs, + write_fs=self.write_fs, + listing_file_system=self.fs, + version='1.2.3', + volumes=self.fs, + ) + + def test_should_print_version(self): + res = self.cmd_run(['trash-restore', '--version']) + + assert 'trash-restore 1.2.3\n' == res.stdout + + def test_with_no_args_and_no_files_in_trashcan(self): + res = self.cmd_run(['trash-restore'], from_dir='cwd') + + assert ("No files trashed from current dir ('cwd')\n" == + res.stdout) + + def test_restore_operation(self): + self.fs.add_trash_file('/cwd/parent/foo.txt', '/data_home/Trash', + datetime.datetime(2016, 1, 1), 'boo') + + res = self.cmd_run(['trash-restore'], reply='0', from_dir='/cwd') + + assert '' == res.stderr + assert ([call.mkdirs('/cwd/parent'), + call.move('/data_home/Trash/files/foo.txt', + '/cwd/parent/foo.txt'), + call.remove_file('/data_home/Trash/info/foo.txt.trashinfo')] + == self.write_fs.mock_calls) + + def test_restore_operation_when_dest_exists(self): + self.fs.add_trash_file('/cwd/parent/foo.txt', '/data_home/Trash', + datetime.datetime(2016, 1, 1), 'boo') + self.fs.add_file('/cwd/parent/foo.txt') + + res = self.cmd_run(['trash-restore'], reply='0', from_dir='/cwd') + + assert 'Refusing to overwrite existing file "foo.txt".\n' == res.stderr + assert ([] == self.write_fs.mock_calls) + + def test_when_user_reply_with_empty_string(self): + self.fs.add_trash_file('/cwd/parent/foo.txt', '/data_home/Trash', + datetime.datetime(2016, 1, 1), 'boo') + + res = self.cmd_run(['trash-restore'], reply='', from_dir='/cwd') + + assert res.last_line_of_stdout() == 'Exiting' + + def test_when_user_reply_with_not_number(self): + self.fs.add_trash_file('/cwd/parent/foo.txt', '/data_home/Trash', + datetime.datetime(2016, 1, 1), 'boo') + + res = self.cmd_run(['trash-restore'], reply='non numeric', from_dir='/cwd') + + assert res.last_line_of_stderr() == \ + 'Invalid entry: not an index: non numeric' + assert 1 == res.exit_code + + def cmd_run(self, args, reply=None, from_dir=None): + return self.user.run_restore(args, reply=reply, from_dir=from_dir) diff -Nru trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_user/test_restore_with_real_fs.py trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_user/test_restore_with_real_fs.py --- trash-cli-0.23.2.13.2/tests/test_restore/use_cases/restore_user/test_restore_with_real_fs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_restore/use_cases/restore_user/test_restore_with_real_fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,118 @@ +import os +import unittest + +import pytest + +from tests.support.asserts.assert_that import assert_that +from tests.support.my_path import MyPath +from tests.test_restore.support.a_trashed_file import ATrashedFile +from tests.test_restore.support.has_been_restored_matcher import \ + has_been_restored +from tests.test_restore.support.restore_file_fixture import RestoreFileFixture +from tests.test_restore.support.restore_user import RestoreUser +from trashcli.fs import RealExists +from trashcli.fstab.volumes import FakeVolumes +from trashcli.restore.file_system import RealFileReader, \ + RealRestoreReadFileSystem, RealRestoreWriteFileSystem, RealListingFileSystem + + +@pytest.mark.slow +class TestRestoreTrash(unittest.TestCase): + def setUp(self): + self.tmp_dir = MyPath.make_temp_dir() + self.fixture = RestoreFileFixture(self.tmp_dir / 'XDG_DATA_HOME') + self.fs = RealExists() + self.cwd = self.tmp_dir / "cwd" + XDG_DATA_HOME = self.tmp_dir / 'XDG_DATA_HOME' + self.trash_dir = XDG_DATA_HOME / 'Trash' + self.user = RestoreUser(environ={'XDG_DATA_HOME': XDG_DATA_HOME}, + uid=os.getuid(), + file_reader=RealFileReader(), + read_fs=RealRestoreReadFileSystem(), + write_fs=RealRestoreWriteFileSystem(), + listing_file_system=RealListingFileSystem(), + version='0.0.0', + volumes=FakeVolumes([])) + + def test_it_does_nothing_when_no_file_have_been_found_in_current_dir(self): + res = self.user.run_restore(from_dir='/') + + self.assertEqual("No files trashed from current dir ('/')\n", + res.output()) + + def test_gives_an_error_on_not_a_number_input(self): + self.fixture.having_a_trashed_file('/foo/bar') + + res = self.user.run_restore(reply='+@notanumber', from_dir='/foo') + + self.assertEqual('Invalid entry: not an index: +@notanumber\n', + res.stderr) + + def test_it_gives_error_when_user_input_is_too_small(self): + self.fixture.having_a_trashed_file('/foo/bar') + + res = self.user.run_restore(reply='1', from_dir='/foo') + + self.assertEqual('Invalid entry: out of range 0..0: 1\n', + res.stderr) + + def test_it_gives_error_when_user_input_is_too_large(self): + self.fixture.having_a_trashed_file('/foo/bar') + + res = self.user.run_restore(reply='1', from_dir='/foo') + + self.assertEqual('Invalid entry: out of range 0..0: 1\n', + res.stderr) + + def test_it_shows_the_file_deleted_from_the_current_dir(self): + self.fixture.having_a_trashed_file('/foo/bar') + + res = self.user.run_restore(reply='', from_dir='/foo') + + self.assertEqual(' 0 2000-01-01 00:00:01 /foo/bar\n' + 'Exiting\n', res.output()) + self.assertEqual('', res.stderr) + + def test_it_restores_the_file_selected_by_the_user(self): + self.fixture.having_a_trashed_file(self.cwd / 'foo') + + self.user.run_restore(reply='0', from_dir=self.cwd) + + self.fixture.file_should_have_been_restored(self.cwd / 'foo') + + def test_it_refuses_overwriting_existing_file(self): + self.fixture.having_a_trashed_file(self.cwd / 'foo') + self.fixture.make_file(self.cwd / "foo") + + res = self.user.run_restore(reply='0', from_dir=self.cwd) + + self.assertEqual('Refusing to overwrite existing file "foo".\n', + res.stderr) + + def test_it_restores_the_file_and_delete_the_trash_info(self): + a_trashed_file = self.make_trashed_file() + + res = self.user.run_restore(reply='0', from_dir=self.cwd) + + assert res.stderr == '' + assert_that(a_trashed_file, has_been_restored(self.fs)) + + def make_trashed_file(self): # type: () -> ATrashedFile + original_location = self.cwd / 'parent/path' + backup_copy = self.trash_dir / 'files/path' + info_file = self.trash_dir / 'info/path.trashinfo' + + self.fixture.make_file(info_file, + '[Trash Info]\n' + 'Path=%s\n' % original_location + + 'DeletionDate=2000-01-01T00:00:01\n') + self.fixture.make_empty_file(backup_copy) + + return ATrashedFile( + trashed_from=self.cwd / 'parent/path', + info_file=self.trash_dir / 'info/path.trashinfo', + backup_copy=self.trash_dir / 'files/path', + ) + + def tearDown(self): + self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_rm/test_filter.py trash-cli-0.23.11.10/tests/test_rm/test_filter.py --- trash-cli-0.23.2.13.2/tests/test_rm/test_filter.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_rm/test_filter.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,38 @@ +import unittest + +from trashcli.rm.filter import Filter + + +class TestFilter(unittest.TestCase): + + def test_a_star_matches_all(self): + self.cmd = Filter('*') + + assert self.cmd.matches('foo') == True + assert self.cmd.matches('bar') == True + + def test_basename_matches(self): + self.cmd = Filter('foo') + + assert self.cmd.matches('foo') == True + assert self.cmd.matches('bar') == False + + def test_example_with_star_dot_o(self): + self.cmd = Filter('*.o') + + assert self.cmd.matches('/foo.h') == False + assert self.cmd.matches('/foo.c') == False + assert self.cmd.matches('/foo.o') == True + assert self.cmd.matches('/bar.o') == True + + def test_absolute_pattern(self): + self.cmd = Filter('/foo/bar.baz') + + assert self.cmd.matches('/foo/bar.baz') == True + assert self.cmd.matches('/foo/bar') == False + + def test(self): + self.cmd = Filter('/foo/*.baz') + + assert self.cmd.matches('/foo/bar.baz') == True + assert self.cmd.matches('/foo/bar.bar') == False diff -Nru trash-cli-0.23.2.13.2/tests/test_rm/test_list_trash_info.py trash-cli-0.23.11.10/tests/test_rm/test_list_trash_info.py --- trash-cli-0.23.2.13.2/tests/test_rm/test_list_trash_info.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_rm/test_list_trash_info.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,38 @@ +import unittest + +import pytest + +from tests.fake_trash_dir import FakeTrashDir +from tests.support.my_path import MyPath +from trashcli.file_system_reader import FileSystemReader +from trashcli.rm.list_trashinfo import ListTrashinfos + + +@pytest.mark.slow +class TestListTrashinfos(unittest.TestCase): + def setUp(self): + self.tmp_dir = MyPath.make_temp_dir() + self.trash_dir = self.tmp_dir / 'Trash' + self.fake_trash_dir = FakeTrashDir(self.trash_dir) + self.listing = ListTrashinfos.make(FileSystemReader(), FileSystemReader()) + + def test_absolute_path(self): + self.fake_trash_dir.add_trashinfo_basename_path('a', '/foo') + + result = list(self.listing.list_from_volume_trashdir(self.trash_dir, + '/volume/')) + + assert result == [('trashed_file', + ('/foo', '%s/info/a.trashinfo' % self.trash_dir))] + + def test_relative_path(self): + self.fake_trash_dir.add_trashinfo_basename_path('a', 'foo') + + result = list(self.listing.list_from_volume_trashdir(self.trash_dir, + '/volume/')) + + assert result == [('trashed_file', + ('/volume/foo', '%s/info/a.trashinfo' % self.trash_dir))] + + def tearDown(self): + self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_rm/test_trash_rm.py trash-cli-0.23.11.10/tests/test_rm/test_trash_rm.py --- trash-cli-0.23.2.13.2/tests/test_rm/test_trash_rm.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_rm/test_trash_rm.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,37 @@ +import unittest + +from six import StringIO + +from mock import Mock + +from tests.support.asserts import assert_starts_with +from trashcli.rm.rm_cmd import RmCmd + + +class TestTrashRmCmdRun(unittest.TestCase): + def setUp(self): + self.volumes_listing = Mock() + self.stderr = StringIO() + self.file_reader = Mock([]) + self.file_reader.exists = Mock([], return_value=None) + self.file_reader.entries_if_dir_exists = Mock([], return_value=[]) + self.environ = {} + self.getuid = lambda: '111' + self.cmd = RmCmd(self.environ, + self.getuid, + self.volumes_listing, + self.stderr, + self.file_reader) + + def test_without_arguments(self): + self.cmd.run([None], uid=None) + + assert_starts_with(self.stderr.getvalue(), + 'Usage:\n trash-rm PATTERN\n\nPlease specify PATTERN.\n') + + def test_without_pattern_argument(self): + self.volumes_listing.list_volumes.return_value = ['/vol1'] + + self.cmd.run([None, None], uid=None) + + assert '' == self.stderr.getvalue() diff -Nru trash-cli-0.23.2.13.2/tests/test_rm/test_trash_rm_slow.py trash-cli-0.23.11.10/tests/test_rm/test_trash_rm_slow.py --- trash-cli-0.23.2.13.2/tests/test_rm/test_trash_rm_slow.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_rm/test_trash_rm_slow.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,43 @@ +import unittest + +import pytest +from six import StringIO + +from tests.fake_trash_dir import FakeTrashDir +from tests.support.my_path import MyPath +from trashcli.fstab.volume_listing import NoVolumesListing +from trashcli.rm.main import RealRmFileSystemReader +from trashcli.rm.rm_cmd import RmCmd + + +@pytest.mark.slow +class TestTrashRm(unittest.TestCase): + def setUp(self): + self.xdg_data_home = MyPath.make_temp_dir() + self.stderr = StringIO() + self.trash_rm = RmCmd(environ={'XDG_DATA_HOME': self.xdg_data_home}, + getuid=lambda: 123, + volumes_listing=NoVolumesListing(), + stderr=self.stderr, + file_reader=RealRmFileSystemReader()) + self.fake_trash_dir = FakeTrashDir(self.xdg_data_home / 'Trash') + + def test_issue69(self): + self.fake_trash_dir.add_trashinfo_without_path('foo') + + self.trash_rm.run(['trash-rm', 'ignored'], uid=None) + + assert (self.stderr.getvalue() == + "trash-rm: %s/Trash/info/foo.trashinfo: unable to parse 'Path'" + '\n' % self.xdg_data_home) + + def test_integration(self): + self.fake_trash_dir.add_trashinfo_basename_path("del", 'to/be/deleted') + self.fake_trash_dir.add_trashinfo_basename_path("keep", 'to/be/kept') + + self.trash_rm.run(['trash-rm', 'delete*'], uid=None) + + assert self.fake_trash_dir.ls_info() == ['keep.trashinfo'] + + def tearDown(self): + self.xdg_data_home.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_support/files/test_make_unreadable_dir.py trash-cli-0.23.11.10/tests/test_support/files/test_make_unreadable_dir.py --- trash-cli-0.23.2.13.2/tests/test_support/files/test_make_unreadable_dir.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_support/files/test_make_unreadable_dir.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,33 @@ +import errno +import os +import shutil +import unittest + +from trashcli.fs import remove_file2 + +from ...support.files import make_unreadable_dir, \ + make_readable +from ...support.my_path import MyPath + + +class Test_make_unreadable_dir(unittest.TestCase): + def setUp(self): + self.tmp_dir = MyPath.make_temp_dir() + self.unreadable_dir = self.tmp_dir / 'unreadable-dir' + + make_unreadable_dir(self.unreadable_dir) + + def test_the_directory_has_been_created(self): + assert os.path.exists(self.unreadable_dir) + + def test_and_can_not_be_removed(self): + try: + remove_file2(self.unreadable_dir) + self.fail() + except OSError as e: + self.assertEqual(errno.errorcode[e.errno], 'EACCES') + + def tearDown(self): + make_readable(self.unreadable_dir) + shutil.rmtree(self.unreadable_dir) + self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_support/files/test_make_unreadable_file.py trash-cli-0.23.11.10/tests/test_support/files/test_make_unreadable_file.py --- trash-cli-0.23.2.13.2/tests/test_support/files/test_make_unreadable_file.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_support/files/test_make_unreadable_file.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,26 @@ +import errno +import os +import shutil +import unittest + +import pytest +from trashcli.fs import read_file + +from ...support.files import make_unreadable_file, make_unreadable_dir, \ + make_readable +from ...support.my_path import MyPath + + +@pytest.mark.slow +class Test_make_unreadable_file(unittest.TestCase): + def setUp(self): + self.tmp_dir = MyPath.make_temp_dir() + + def test(self): + path = self.tmp_dir / "unreadable" + make_unreadable_file(self.tmp_dir / "unreadable") + with self.assertRaises(IOError): + read_file(path) + + def tearDown(self): + self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_support/test_help_reformatting/test_normalize_spaces.py trash-cli-0.23.11.10/tests/test_support/test_help_reformatting/test_normalize_spaces.py --- trash-cli-0.23.2.13.2/tests/test_support/test_help_reformatting/test_normalize_spaces.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_support/test_help_reformatting/test_normalize_spaces.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,13 @@ +from tests.support.help_reformatting import normalize_spaces + + +class TestNormalizeSpaces: + def test(self): + text = """usage: trash-list [-h] [--print-completion {bash,zsh,tcsh}] [--version] + [--volumes] [--trash-dirs] [--trash-dir TRASH_DIRS] + [--all-users]""" + + assert normalize_spaces(text) == ( + "usage: trash-list [-h] [--print-completion {bash,zsh,tcsh}] " + "[--version] [--volumes] [--trash-dirs] [--trash-dir TRASH_DIRS] " + "[--all-users]") diff -Nru trash-cli-0.23.2.13.2/tests/test_support/test_help_reformatting/test_parse_help.py trash-cli-0.23.11.10/tests/test_support/test_help_reformatting/test_parse_help.py --- trash-cli-0.23.2.13.2/tests/test_support/test_help_reformatting/test_parse_help.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_support/test_help_reformatting/test_parse_help.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,76 @@ +from tests.support.help_reformatting import reformat_help_message, split_paragraphs + + +class TestParseHelp: + def test_format_help_message(self): + assert reformat_help_message(self.help_message) == ( + 'usage: trash-list [-h] [--print-completion {bash,zsh,tcsh}] [--version] ' + '[--volumes] [--trash-dirs] [--trash-dir TRASH_DIRS] ' + '[--all-users]\n' + '\n' + 'List trashed files\n' + '\n' + 'options:\n' + ' -h, --help show this help message and exit\n' + ' --print-completion {bash,zsh,tcsh}\n' + ' print shell completion script\n' + " --version show program's version number and exit\n" + ' --volumes list volumes\n' + ' --trash-dirs list trash dirs\n' + ' --trash-dir TRASH_DIRS\n' + ' specify the trash directory to use\n' + ' --all-users list trashcans of all the users\n' + '\n' + 'Report bugs to https://github.com/andreafrancia/trash-cli/issues\n') + + def test_first(self): + assert split_paragraphs(self.help_message)[0] == ( + 'usage: trash-list [-h] [--print-completion {bash,zsh,tcsh}] [--version]\n' + ' [--volumes] [--trash-dirs] [--trash-dir TRASH_DIRS]\n' + ' [--all-users]\n') + + def test_second(self): + assert split_paragraphs(self.help_message)[1] == ( + 'List trashed files\n') + + def test_third(self): + assert split_paragraphs(self.help_message)[2] == ( + 'options:\n' + ' -h, --help show this help message and exit\n' + ' --print-completion {bash,zsh,tcsh}\n' + ' print shell completion script\n' + " --version show program's version number and exit\n" + ' --volumes list volumes\n' + ' --trash-dirs list trash dirs\n' + ' --trash-dir TRASH_DIRS\n' + ' specify the trash directory to use\n' + ' --all-users list trashcans of all the users\n') + + def test_fourth(self): + assert split_paragraphs(self.help_message)[3] == ( + 'Report bugs to https://github.com/andreafrancia/trash-cli/issues\n' + ) + + def test_only_four(self): + assert len(split_paragraphs(self.help_message)) == 4 + + help_message = """\ +usage: trash-list [-h] [--print-completion {bash,zsh,tcsh}] [--version] + [--volumes] [--trash-dirs] [--trash-dir TRASH_DIRS] + [--all-users] + +List trashed files + +options: + -h, --help show this help message and exit + --print-completion {bash,zsh,tcsh} + print shell completion script + --version show program's version number and exit + --volumes list volumes + --trash-dirs list trash dirs + --trash-dir TRASH_DIRS + specify the trash directory to use + --all-users list trashcans of all the users + +Report bugs to https://github.com/andreafrancia/trash-cli/issues +""" diff -Nru trash-cli-0.23.2.13.2/tests/test_support/test_help_reformatting/test_split_paragraphs.py trash-cli-0.23.11.10/tests/test_support/test_help_reformatting/test_split_paragraphs.py --- trash-cli-0.23.2.13.2/tests/test_support/test_help_reformatting/test_split_paragraphs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_support/test_help_reformatting/test_split_paragraphs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,14 @@ +from parameterized import parameterized # type: ignore + +from tests.support.help_reformatting import split_paragraphs + + +@parameterized.expand([ + ('one line', ['one line']), + ('one line\n', ['one line\n']), + ('one\ntwo\n', ['one\ntwo\n']), + ('one\n\ntwo\n', ['one\n', 'two\n']), + ('one\n \ntwo\n', ['one\n', 'two\n']), +]) +def test_split_paragraphs(text, expected_result): + assert split_paragraphs(text) == expected_result diff -Nru trash-cli-0.23.2.13.2/tests/test_top_trash_dir_rules.py trash-cli-0.23.11.10/tests/test_top_trash_dir_rules.py --- trash-cli-0.23.2.13.2/tests/test_top_trash_dir_rules.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_top_trash_dir_rules.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,73 +0,0 @@ -import unittest - -from mock import Mock, call -from trashcli.put.candidate import Candidate - -from trashcli.put.security_check import SecurityCheck, TopTrashDirCheck - - -class TestTopTrashDirRules(unittest.TestCase): - def setUp(self): - self.fs = Mock(spec=['isdir', 'islink', 'has_sticky_bit']) - self.check = SecurityCheck(self.fs) - - def test_not_valid_should_be_a_dir(self): - self.fs.isdir.return_value = False - - secure, messages = self.check.check_trash_dir_is_secure( - make_candidate('/parent/trash-dir', TopTrashDirCheck)) - - assert [call.isdir('/parent')] == self.fs.mock_calls - assert secure == False - assert messages == [ - 'found unusable .Trash dir (should be a dir): /parent'] - - def test_not_valid_parent_should_not_be_a_symlink(self): - self.fs.isdir.return_value = True - self.fs.islink.return_value = True - - secure, messages = self.check.check_trash_dir_is_secure( - make_candidate('/parent/trash-dir', TopTrashDirCheck)) - - assert [call.isdir('/parent'), - call.islink('/parent')] == self.fs.mock_calls - assert secure == False - assert messages == [ - 'found unsecure .Trash dir (should not be a symlink): /parent'] - - def test_not_valid_parent_should_be_sticky(self): - self.fs.isdir.return_value = True - self.fs.islink.return_value = False - self.fs.has_sticky_bit.return_value = False - - secure, messages = self.check.check_trash_dir_is_secure( - make_candidate('/parent/trash-dir', TopTrashDirCheck)) - - assert [call.isdir('/parent'), - call.islink('/parent'), - call.has_sticky_bit('/parent')] == self.fs.mock_calls - assert False == secure - assert messages == [ - 'found unsecure .Trash dir (should be sticky): /parent'] - - def test_is_valid(self): - self.fs.isdir.return_value = True - self.fs.islink.return_value = False - self.fs.has_sticky_bit.return_value = True - - secure, messages = self.check.check_trash_dir_is_secure( - make_candidate('/parent/trash-dir', TopTrashDirCheck)) - - assert [call.isdir('/parent'), - call.islink('/parent'), - call.has_sticky_bit('/parent')] == self.fs.mock_calls - assert secure == True - assert messages == [] - - -def make_candidate(trash_dir_path, check_type): - return Candidate(trash_dir_path=trash_dir_path, - volume=None, - path_maker_type=None, - check_type=check_type, - gate=None) diff -Nru trash-cli-0.23.2.13.2/tests/test_trash_dir_reader.py trash-cli-0.23.11.10/tests/test_trash_dir_reader.py --- trash-cli-0.23.2.13.2/tests/test_trash_dir_reader.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_trash_dir_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -3,7 +3,7 @@ from tests.fake_file_system import FakeFileSystem -from trashcli.trash import TrashDirReader +from trashcli.lib.trash_dir_reader import TrashDirReader class TestTrashDirReader(unittest.TestCase): @@ -24,5 +24,3 @@ result = list(self.trash_dir.list_orphans('/')) assert ['/files/foo'] == result - - diff -Nru trash-cli-0.23.2.13.2/tests/test_trash_list.py trash-cli-0.23.11.10/tests/test_trash_list.py --- trash-cli-0.23.2.13.2/tests/test_trash_list.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_trash_list.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,259 +0,0 @@ -# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy -import os -import unittest -from datetime import datetime - -import pytest -from mock import Mock -from trashcli import trash -from trashcli.fs import FileSystemReader -from trashcli.fstab import VolumesListing -from trashcli.list import ListCmd - -from .support.asserts import assert_equals_with_unidiff -from .fake_trash_dir import FakeTrashDir -from .support.files import require_empty_dir, make_sticky_dir, make_unsticky_dir -from .output_collector import OutputCollector -from .support.volumes_mock import volumes_mock -from .support.my_path import MyPath - - -@pytest.mark.slow -class Setup(unittest.TestCase): - def setUp(self): - self.temp_dir = MyPath.make_temp_dir() - self.xdg_data_home = MyPath.make_temp_dir() - self.top_dir = self.temp_dir / "topdir" - require_empty_dir(self.top_dir) - self.user = TrashListUser(self.xdg_data_home) - - def tearDown(self): - self.xdg_data_home.clean_up() - self.temp_dir.clean_up() - - -def sort_lines(lines): - return "".join(sorted(lines.splitlines(True))) - - -class Test_describe_trash_list(Setup): - - def test_should_output_the_version(self): - self.user.run_trash_list('--version') - - assert_equals_with_unidiff('trash-list %s\n' % trash.version, - self.user.output()) - - def test_should_output_nothing_when_trashcan_is_empty(self): - self.user.run_trash_list() - - assert_equals_with_unidiff('', self.user.output()) - - def test_should_output_deletion_date_and_path(self): - self.user.home_trashdir.add_trashinfo2('/absolute/path', - datetime(2001, 2, 3, 23, 55, 59)) - - self.user.run_trash_list() - - assert_equals_with_unidiff("2001-02-03 23:55:59 /absolute/path\n", - self.user.output()) - - def test_should_output_info_for_multiple_files(self): - self.user.home_trashdir.add_trashinfo2("/file1", datetime(2000, 1, 1, 0, 0, 1)) - self.user.home_trashdir.add_trashinfo2("/file2", datetime(2000, 1, 1, 0, 0, 2)) - self.user.home_trashdir.add_trashinfo2("/file3", datetime(2000, 1, 1, 0, 0, 3)) - - self.user.run_trash_list() - output = self.user.output() - - assert_equals_with_unidiff("2000-01-01 00:00:01 /file1\n" - "2000-01-01 00:00:02 /file2\n" - "2000-01-01 00:00:03 /file3\n", - sort_lines(output)) - - def test_should_output_unknown_dates_with_question_marks(self): - self.user.home_trashdir.add_trashinfo_without_date('without-date') - - self.user.run_trash_list() - - assert self.user.output() == "????-??-?? ??:??:?? /without-date\n" - - def test_should_output_invalid_dates_using_question_marks(self): - self.user.home_trashdir.add_trashinfo_wrong_date('with-invalid-date', - 'Wrong date') - - self.user.run_trash_list() - - assert_equals_with_unidiff("????-??-?? ??:??:?? /with-invalid-date\n", - self.user.output()) - - def test_should_warn_about_empty_trashinfos(self): - self.user.home_trashdir.add_trashinfo_content('empty', '') - - self.user.run_trash_list() - - assert_equals_with_unidiff( - "Parse Error: %(XDG_DATA_HOME)s/Trash/info/empty.trashinfo: " - "Unable to parse Path.\n" % {"XDG_DATA_HOME": self.xdg_data_home}, - self.user.error()) - - def test_should_warn_about_unreadable_trashinfo(self): - self.user.home_trashdir.add_unreadable_trashinfo('unreadable') - - self.user.run_trash_list() - - assert_equals_with_unidiff( - "[Errno 13] Permission denied: " - "'%(XDG_DATA_HOME)s/Trash/info/unreadable.trashinfo'\n" % { - 'XDG_DATA_HOME': self.xdg_data_home - }, - self.user.error()) - - def test_should_warn_about_unexistent_path_entry(self): - self.user.home_trashdir.add_trashinfo_without_path("foo") - - self.user.run_trash_list() - - assert_equals_with_unidiff( - "Parse Error: %(XDG_DATA_HOME)s/Trash/info/foo.trashinfo: " - "Unable to parse Path.\n" % { - 'XDG_DATA_HOME': self.xdg_data_home}, - self.user.error()) - assert_equals_with_unidiff('', self.user.output()) - - -class Test_with_a_top_trash_dir(Setup): - def setUp(self): - super(type(self), self).setUp() - self.top_trashdir1 = FakeTrashDir(self.top_dir / '.Trash/123') - self.user.set_fake_uid(123) - self.user.add_volume(self.top_dir) - - def test_should_list_its_contents_if_parent_is_sticky(self): - make_sticky_dir(self.top_dir / '.Trash') - self.and_contains_a_valid_trashinfo() - - self.user.run_trash_list() - - assert_equals_with_unidiff("2000-01-01 00:00:00 %s/file1\n" % self.top_dir, - self.user.output()) - - def test_and_should_warn_if_parent_is_not_sticky(self): - make_unsticky_dir(self.top_dir / '.Trash') - self.and_dir_exists(self.top_dir / '.Trash/123') - - self.user.run_trash_list() - - assert_equals_with_unidiff( - "TrashDir skipped because parent not sticky: %s/.Trash/123\n" % - self.top_dir, - self.user.error() - ) - - def test_but_it_should_not_warn_when_the_parent_is_unsticky_but_there_is_no_trashdir(self): - make_unsticky_dir(self.top_dir / '.Trash') - self.but_does_not_exists_any(self.top_dir / '.Trash/123') - - self.user.run_trash_list() - - assert_equals_with_unidiff("", self.user.error()) - - def test_should_ignore_trash_from_a_unsticky_topdir(self): - make_unsticky_dir(self.top_dir / '.Trash') - self.and_contains_a_valid_trashinfo() - - self.user.run_trash_list() - - assert_equals_with_unidiff('', self.user.output()) - - def test_it_should_ignore_Trash_is_a_symlink(self): - self.when_is_a_symlink_to_a_dir(self.top_dir / '.Trash') - self.and_contains_a_valid_trashinfo() - - self.user.run_trash_list() - - assert_equals_with_unidiff('', self.user.output()) - - def test_and_should_warn_about_it(self): - self.when_is_a_symlink_to_a_dir(self.top_dir / '.Trash') - self.and_contains_a_valid_trashinfo() - - self.user.run_trash_list() - - assert_equals_with_unidiff( - 'TrashDir skipped because parent not sticky: %s/.Trash/123\n' % - self.top_dir, - self.user.error() - ) - - def but_does_not_exists_any(self, path): - assert not os.path.exists(path) - - def and_dir_exists(self, path): - os.mkdir(path) - assert os.path.isdir(path) - - def and_contains_a_valid_trashinfo(self): - self.top_trashdir1.add_trashinfo2('file1', datetime(2000, 1, 1, 0, 0, 0)) - - def when_is_a_symlink_to_a_dir(self, path): - dest = "%s-dest" % path - os.mkdir(dest) - rel_dest = os.path.basename(dest) - os.symlink(rel_dest, path) - - -class Test_describe_when_a_file_is_in_alternate_top_trashdir(Setup): - - def test_should_list_contents_of_alternate_trashdir(self): - self.user.set_fake_uid(123) - self.user.add_volume(self.top_dir) - self.top_trashdir2 = FakeTrashDir(self.top_dir / '.Trash-123') - self.top_trashdir2.add_trashinfo2('file', datetime(2000, 1, 1, 0, 0, 0)) - - self.user.run_trash_list() - - assert_equals_with_unidiff("2000-01-01 00:00:00 %s/file\n" % - self.top_dir, - self.user.output()) - - -class TrashListUser: - def __init__(self, xdg_data_home): - self.stdout = OutputCollector() - self.stderr = OutputCollector() - self.environ = {'XDG_DATA_HOME': xdg_data_home} - self.fake_uid = None - self.volumes = [] - trash_dir = os.path.join(xdg_data_home, "Trash") - self.home_trashdir = FakeTrashDir(trash_dir) - - def run_trash_list(self, *args): - self.run('trash-list', *args) - - def run(self, *argv): - file_reader = FileSystemReader() - file_reader.list_volumes = lambda: self.volumes - volumes_listing = Mock(spec=VolumesListing) - volumes_listing.list_volumes.return_value = self.volumes - ListCmd( - out=self.stdout, - err=self.stderr, - environ=self.environ, - uid=self.fake_uid, - file_reader=file_reader, - volumes_listing=volumes_listing, - volumes=volumes_mock() - ).run(argv) - - def set_fake_uid(self, uid): - self.fake_uid = uid - - def add_volume(self, mount_point): - self.volumes.append(mount_point) - - def error(self): - return self.stderr.getvalue() - - def output(self): - return self.stdout.getvalue() diff -Nru trash-cli-0.23.2.13.2/tests/test_trash_put_reporter.py trash-cli-0.23.11.10/tests/test_trash_put_reporter.py --- trash-cli-0.23.2.13.2/tests/test_trash_put_reporter.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_trash_put_reporter.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,7 @@ import unittest -from mock import Mock, call +from mock import Mock +from mock import call from trashcli.put.my_logger import LogData from trashcli.put.reporter import TrashPutReporter @@ -14,7 +15,7 @@ self.reporter = TrashPutReporter(self.logger, describer) def test_it_should_record_failures(self): - self.reporter.unable_to_trash_file('a file', LogData('trash-put', 99)) + self.reporter.unable_to_trash_file_non_existent('a file', LogData('trash-put', 99)) assert [call('cannot trash file-description \'a file\'', 'trash-put')] == \ self.logger.warning2.mock_calls diff -Nru trash-cli-0.23.2.13.2/tests/test_trash_rm.py trash-cli-0.23.11.10/tests/test_trash_rm.py --- trash-cli-0.23.2.13.2/tests/test_trash_rm.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_trash_rm.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -import unittest - -from six import StringIO - -from mock import Mock -from trashcli.rm import Filter - - -class TestTrashRmCmdRun(unittest.TestCase): - def test_without_arguments(self): - from trashcli.rm import RmCmd - cmd = RmCmd(None, None, None, None, None) - cmd.stderr = StringIO() - cmd.run([None], uid=None) - - self.assertStartsWith(cmd.stderr.getvalue(), - 'Usage:\n trash-rm PATTERN\n\nPlease specify PATTERN.\n') - - def assertStartsWith(self, actual, expected): - self.assertEqual(actual[:len(expected)], expected) - - def test_without_pattern_argument(self): - from trashcli.rm import RmCmd - volumes_listing = Mock() - cmd = RmCmd(None, None, volumes_listing, None, None) - cmd.stderr = StringIO() - cmd.file_reader = Mock([]) - cmd.file_reader.exists = Mock([], return_value=None) - cmd.file_reader.entries_if_dir_exists = Mock([], return_value=[]) - cmd.environ = {} - cmd.getuid = lambda: '111' - volumes_listing.list_volumes.return_value = ['/vol1'] - - cmd.run([None, None], uid=None) - - assert '' == cmd.stderr.getvalue() - - -class TestTrashRmCmd(unittest.TestCase): - - def test_a_star_matches_all(self): - self.cmd = Filter('*') - - assert self.cmd.matches('foo') == True - assert self.cmd.matches('bar') == True - - def test_basename_matches(self): - self.cmd = Filter('foo') - - assert self.cmd.matches('foo') == True - assert self.cmd.matches('bar') == False - - def test_example_with_star_dot_o(self): - self.cmd = Filter('*.o') - - assert self.cmd.matches('/foo.h') == False - assert self.cmd.matches('/foo.c') == False - assert self.cmd.matches('/foo.o') == True - assert self.cmd.matches('/bar.o') == True - - def test_absolute_pattern(self): - self.cmd = Filter('/foo/bar.baz') - - assert self.cmd.matches('/foo/bar.baz') == True - assert self.cmd.matches('/foo/bar') == False - - def test(self): - self.cmd = Filter('/foo/*.baz') - - assert self.cmd.matches('/foo/bar.baz') == True - assert self.cmd.matches('/foo/bar.bar') == False diff -Nru trash-cli-0.23.2.13.2/tests/test_trash_rm_slow.py trash-cli-0.23.11.10/tests/test_trash_rm_slow.py --- trash-cli-0.23.2.13.2/tests/test_trash_rm_slow.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_trash_rm_slow.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,74 +0,0 @@ -import unittest - -from six import StringIO - -import pytest -from trashcli.fs import FileSystemReader -from trashcli.fstab import VolumesListing -from trashcli.rm import ListTrashinfos, RmCmd - -from .fake_trash_dir import FakeTrashDir -from .support.my_path import MyPath - - -@pytest.mark.slow -class TestTrashRm(unittest.TestCase): - def setUp(self): - self.xdg_data_home = MyPath.make_temp_dir() - self.stderr = StringIO() - self.trash_rm = RmCmd(environ={'XDG_DATA_HOME': self.xdg_data_home} - , getuid=lambda: 123 - , volumes_listing=VolumesListing(lambda: []) - , stderr=self.stderr - , file_reader=FileSystemReader()) - self.fake_trash_dir = FakeTrashDir(self.xdg_data_home / 'Trash') - - def test_issue69(self): - self.fake_trash_dir.add_trashinfo_without_path('foo') - - self.trash_rm.run(['trash-rm', 'ignored'], uid=None) - - assert (self.stderr.getvalue() == - "trash-rm: %s/Trash/info/foo.trashinfo: unable to parse 'Path'" - '\n' % self.xdg_data_home) - - def test_integration(self): - self.fake_trash_dir.add_trashinfo_basename_path("del", 'to/be/deleted') - self.fake_trash_dir.add_trashinfo_basename_path("keep", 'to/be/kept') - - self.trash_rm.run(['trash-rm', 'delete*'], uid=None) - - assert self.fake_trash_dir.ls_info() == ['keep.trashinfo'] - - def tearDown(self): - self.xdg_data_home.clean_up() - - -@pytest.mark.slow -class TestListTrashinfos(unittest.TestCase): - def setUp(self): - self.tmp_dir = MyPath.make_temp_dir() - self.trash_dir = self.tmp_dir / 'Trash' - self.fake_trash_dir = FakeTrashDir(self.trash_dir) - self.listing = ListTrashinfos(FileSystemReader()) - - def test_absolute_path(self): - self.fake_trash_dir.add_trashinfo_basename_path('a', '/foo') - - result = list(self.listing.list_from_volume_trashdir(self.trash_dir, - '/volume/')) - - assert result == [('trashed_file', - ('/foo', '%s/info/a.trashinfo' % self.trash_dir))] - - def test_relative_path(self): - self.fake_trash_dir.add_trashinfo_basename_path('a', 'foo') - - result = list(self.listing.list_from_volume_trashdir(self.trash_dir, - '/volume/')) - - assert result == [('trashed_file', - ('/volume/foo', '%s/info/a.trashinfo' % self.trash_dir))] - - def tearDown(self): - self.tmp_dir.clean_up() diff -Nru trash-cli-0.23.2.13.2/tests/test_volume_of.py trash-cli-0.23.11.10/tests/test_volume_of.py --- trash-cli-0.23.2.13.2/tests/test_volume_of.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/test_volume_of.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,21 +1,21 @@ import unittest -from trashcli.fstab import create_fake_volume_of +from tests.support.fake_volume_of import fake_volume_of class Test_create_fake_volume_of(unittest.TestCase): def test_return_the_containing_volume(self): - self.volumes = create_fake_volume_of(['/fake-vol']) + self.volumes = fake_volume_of(['/fake-vol']) assert '/fake-vol' == self.volumes.volume_of('/fake-vol/foo') def test_with_file_that_are_outside(self): - self.volumes = create_fake_volume_of(['/fake-vol']) + self.volumes = fake_volume_of(['/fake-vol']) assert '/' == self.volumes.volume_of('/foo') def test_it_work_also_with_relative_mount_point(self): - self.volumes = create_fake_volume_of(['relative-fake-vol']) + self.volumes = fake_volume_of(['relative-fake-vol']) assert 'relative-fake-vol' == self.volumes.volume_of('relative-fake-vol/foo') diff -Nru trash-cli-0.23.2.13.2/tests/trash_dir_scanner/test_trash_dir_scanner.py trash-cli-0.23.11.10/tests/trash_dir_scanner/test_trash_dir_scanner.py --- trash-cli-0.23.2.13.2/tests/trash_dir_scanner/test_trash_dir_scanner.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/trash_dir_scanner/test_trash_dir_scanner.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,15 +1,17 @@ import unittest from mock import Mock -from trashcli.fstab import VolumesListing -from trashcli.trash import DirChecker, UserInfoProvider + +from trashcli.fstab.volume_listing import VolumesListing +from trashcli.lib.dir_checker import DirChecker +from trashcli.lib.user_info import SingleUserInfoProvider from trashcli.trash_dirs_scanner import TrashDirsScanner, trash_dir_found class TestTrashDirScanner(unittest.TestCase): def test_scan_trash_dirs(self): volumes_listing = Mock(spec=VolumesListing) - user_info_provider = UserInfoProvider() + user_info_provider = SingleUserInfoProvider() dir_checker = Mock(spec=DirChecker) scanner = TrashDirsScanner( user_info_provider, diff -Nru trash-cli-0.23.2.13.2/tests/trash_dir_scanner/test_user_info_provider.py trash-cli-0.23.11.10/tests/trash_dir_scanner/test_user_info_provider.py --- trash-cli-0.23.2.13.2/tests/trash_dir_scanner/test_user_info_provider.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/tests/trash_dir_scanner/test_user_info_provider.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,11 +1,11 @@ import unittest -from trashcli.trash import UserInfoProvider +from trashcli.lib.user_info import SingleUserInfoProvider class TestUserInfoProvider(unittest.TestCase): def setUp(self): - self.provider = UserInfoProvider() + self.provider = SingleUserInfoProvider() def test_getuid(self): diff -Nru trash-cli-0.23.2.13.2/trash-list trash-cli-0.23.11.10/trash-list --- trash-cli-0.23.2.13.2/trash-list 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trash-list 2023-11-10 07:15:04.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python from __future__ import absolute_import import sys -from trashcli.list import main as main +from trashcli.list.main import main as main sys.exit(main()) diff -Nru trash-cli-0.23.2.13.2/trash-restore trash-cli-0.23.11.10/trash-restore --- trash-cli-0.23.2.13.2/trash-restore 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trash-restore 2023-11-10 07:15:04.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python from __future__ import absolute_import import sys -from trashcli.restore import main as main +from trashcli.restore.main import main as main sys.exit(main()) diff -Nru trash-cli-0.23.2.13.2/trash-rm trash-cli-0.23.11.10/trash-rm --- trash-cli-0.23.2.13.2/trash-rm 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trash-rm 2023-11-10 07:15:04.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python from __future__ import absolute_import import sys -from trashcli.rm import main as main +from trashcli.rm.main import main as main sys.exit(main()) diff -Nru trash-cli-0.23.2.13.2/trashcli/compat.py trash-cli-0.23.11.10/trashcli/compat.py --- trash-cli-0.23.2.13.2/trashcli/compat.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/compat.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,12 @@ +def protocol(): + try: + from typing import Protocol + return Protocol + except ImportError as e: + from typing_extensions import Protocol + return Protocol + + +Protocol = protocol() + +del protocol diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/actions.py trash-cli-0.23.11.10/trashcli/empty/actions.py --- trash-cli-0.23.2.13.2/trashcli/empty/actions.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/actions.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -from trashcli.super_enum import SuperEnum - - -class Action(SuperEnum): - empty = 'empty' - print_time = 'print_time' - print_version = 'print_version' diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/clock.py trash-cli-0.23.11.10/trashcli/empty/clock.py --- trash-cli-0.23.2.13.2/trashcli/empty/clock.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/clock.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,20 @@ +from __future__ import absolute_import + +import datetime + + +class Clock: + def __init__(self, real_now, errors): + self.real_now = real_now + self.errors = errors + + def get_now_value(self, environ): + if 'TRASH_DATE' in environ: + try: + return datetime.datetime.strptime(environ['TRASH_DATE'], + "%Y-%m-%dT%H:%M:%S") + except ValueError: + self.errors.print_error('invalid TRASH_DATE: %s' % + environ['TRASH_DATE']) + return self.real_now() + return self.real_now() diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/console.py trash-cli-0.23.11.10/trashcli/empty/console.py --- trash-cli-0.23.2.13.2/trashcli/empty/console.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/console.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,11 +1,11 @@ -from io import TextIOWrapper +from typing import TextIO from trashcli.empty.errors import format_error_msg class Console: def __init__(self, program_name, out, - err): # type: (str, TextIOWrapper, TextIOWrapper) -> None + err): # type: (str, TextIO, TextIO) -> None self.program_name = program_name self.out = out self.err = err diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/delete_according_date.py trash-cli-0.23.11.10/trashcli/empty/delete_according_date.py --- trash-cli-0.23.2.13.2/trashcli/empty/delete_according_date.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/delete_according_date.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,14 +1,14 @@ +from trashcli.empty.clock import Clock from trashcli.empty.older_than import older_than -from trashcli.trash import Clock, parse_deletion_date - - -class ContentReader: - def contents_of(self, path): - raise NotImplementedError +from trashcli.fs import ContentsOf +from trashcli.parse_trashinfo.parse_deletion_date import parse_deletion_date class DeleteAccordingDate: - def __init__(self, reader, clock): # type: (ContentReader, Clock) -> None + def __init__(self, + reader, # type: ContentsOf + clock, # type: Clock + ): self.reader = reader self.clock = clock diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/emptier.py trash-cli-0.23.11.10/trashcli/empty/emptier.py --- trash-cli-0.23.2.13.2/trashcli/empty/emptier.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/emptier.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,10 +1,11 @@ # Copyright (C) 2022 Andrea Francia Bereguardo(PV) Italy -from typing import Iterator +from typing import Iterable from trashcli.empty.console import Console from trashcli.empty.delete_according_date import DeleteAccordingDate from trashcli.empty.existing_file_remover import ExistingFileRemover -from trashcli.trash import TrashDirReader, path_of_backup_copy +from trashcli.lib.path_of_backup_copy import path_of_backup_copy +from trashcli.lib.trash_dir_reader import TrashDirReader from trashcli.trash_dirs_scanner import TrashDir, only_found @@ -16,8 +17,13 @@ self.delete_mode = delete_mode self.trash_dir_reader = trash_dir_reader - def do_empty(self, trash_dirs, environ, parsed_days, - dry_run, verbose): # type: (Iterator[TrashDir], dict, int, bool, int) -> None + def do_empty(self, + trash_dirs, # type: Iterable[TrashDir] + environ, # type: dict + parsed_days, # type: int + dry_run, # type: bool + verbose, # type: int + ): # type: (...) -> None for path in self.files_to_delete(trash_dirs, environ, parsed_days): if dry_run: self.console.print_dry_run(path) @@ -29,8 +35,11 @@ except OSError: self.console.print_cannot_remove_error(path) - def files_to_delete(self, trash_dirs, environ, - parsed_days): # type: (Iterator[TrashDir], dict, int) -> Iterator[str] + def files_to_delete(self, + trash_dirs, # type: Iterable[TrashDir] + environ, # type: dict + parsed_days, # type: int + ): # type: (...) -> Iterable[str] for trash_dir in only_found(trash_dirs): # type: TrashDir for trash_info_path in self.trash_dir_reader.list_trashinfo( trash_dir.path): diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/empty_action.py trash-cli-0.23.11.10/trashcli/empty/empty_action.py --- trash-cli-0.23.2.13.2/trashcli/empty/empty_action.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/empty_action.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,47 +1,72 @@ -from typing import Dict +from typing import NamedTuple, List +from trashcli.empty.clock import Clock from trashcli.empty.console import Console from trashcli.empty.delete_according_date import ( - ContentReader, DeleteAccordingDate, ) from trashcli.empty.emptier import Emptier from trashcli.empty.existing_file_remover import ExistingFileRemover from trashcli.empty.guard import Guard from trashcli.empty.parse_reply import parse_reply -from trashcli.empty.parser import Parsed from trashcli.empty.prepare_output_message import prepare_output_message from trashcli.empty.user import User -from trashcli.fstab import Volumes, VolumesListing -from trashcli.lib.my_input import my_input -from trashcli.list import TrashDirsSelector -from trashcli.trash import Clock, DirReader, TrashDirReader +from trashcli.fs import ContentsOf +from trashcli.fstab.volume_listing import VolumesListing +from trashcli.fstab.volume_of import VolumeOf +from trashcli.lib.dir_reader import DirReader +from trashcli.lib.environ import Environ +from trashcli.lib.my_input import RealInput +from trashcli.lib.trash_dir_reader import TrashDirReader +from trashcli.list.trash_dir_selector import TrashDirsSelector from trashcli.trash_dirs_scanner import TopTrashDirRules +class EmptyActionArgs( + NamedTuple('EmptyActionArgs', [ + ('user_specified_trash_dirs', List[str]), + ('all_users', bool), + ('interactive', bool), + ('days', int), + ('dry_run', bool), + ('verbose', int), + ('environ', Environ), + ('uid', int), + ])): + pass + + class EmptyAction: - def __init__(self, clock, file_remover, volumes_listing, file_reader, - volumes, dir_reader, content_reader, - console): # type: (Clock, ExistingFileRemover, VolumesListing, TopTrashDirRules.Reader, Volumes, DirReader, ContentReader, Console) -> None + def __init__(self, + clock, # type: Clock + file_remover, # type: ExistingFileRemover + volumes_listing, # type: VolumesListing + file_reader, # type: TopTrashDirRules.Reader + volumes, # type: VolumeOf + dir_reader, # type: DirReader + content_reader, # type: ContentsOf + console, # type: Console + ): # type: (...) -> None self.selector = TrashDirsSelector.make(volumes_listing, file_reader, volumes) trash_dir_reader = TrashDirReader(dir_reader) delete_mode = DeleteAccordingDate(content_reader, clock) - user = User(prepare_output_message, my_input, parse_reply) + user = User(prepare_output_message, RealInput(), parse_reply) self.emptier = Emptier(delete_mode, trash_dir_reader, file_remover, console) self.guard = Guard(user) - def run_action(self, parsed, environ, - uid): # type: (Parsed, Dict[str, str], int) -> None - trash_dirs = self.selector.select(parsed.all_users, - parsed.user_specified_trash_dirs, - environ, - uid) - delete_pass = self.guard.ask_the_user(parsed.interactive, + def run_action(self, + args, # type: EmptyActionArgs + ): # type: (...) -> None + trash_dirs = self.selector.select(args.all_users, + args.user_specified_trash_dirs, + args.environ, + args.uid) + delete_pass = self.guard.ask_the_user(args.interactive, trash_dirs) if delete_pass.ok_to_empty: - self.emptier.do_empty(delete_pass.trash_dirs, environ, - parsed.days, parsed.dry_run, parsed.verbose) + self.emptier.do_empty(delete_pass.trash_dirs, args.environ, + args.days, args.dry_run, args.verbose) diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/empty_cmd.py trash-cli-0.23.11.10/trashcli/empty/empty_cmd.py --- trash-cli-0.23.2.13.2/trashcli/empty/empty_cmd.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/empty_cmd.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,35 +1,37 @@ import os from datetime import datetime -from io import TextIOWrapper +from typing import TextIO, Callable -from trashcli.empty.actions import Action +from trashcli.empty.clock import Clock from trashcli.empty.console import Console -from trashcli.empty.delete_according_date import ContentReader -from trashcli.empty.empty_action import EmptyAction +from trashcli.empty.empty_action import EmptyAction, EmptyActionArgs from trashcli.empty.errors import Errors from trashcli.empty.existing_file_remover import ExistingFileRemover from trashcli.empty.is_input_interactive import is_input_interactive from trashcli.empty.parser import Parser -from trashcli.empty.print_time_action import PrintTimeAction -from trashcli.empty.print_version_action import PrintVersionAction -from trashcli.fstab import Volumes, VolumesListing -from trashcli.trash import EX_OK, Clock, DirReader +from trashcli.empty.print_time_action import PrintTimeAction, PrintTimeArgs +from trashcli.fs import ContentsOf +from trashcli.fstab.volume_listing import VolumesListing +from trashcli.fstab.volume_of import VolumeOf +from trashcli.lib.dir_reader import DirReader +from trashcli.lib.exit_codes import EX_OK +from trashcli.lib.print_version import PrintVersionAction, PrintVersionArgs from trashcli.trash_dirs_scanner import TopTrashDirRules class EmptyCmd: def __init__(self, argv0, # type: str - out, # type: TextIOWrapper - err, # type: TextIOWrapper + out, # type: TextIO + err, # type: TextIO volumes_listing, # type: VolumesListing - now, # type: () -> datetime + now, # type: Callable[[], datetime] file_reader, # type: TopTrashDirRules.Reader dir_reader, # type: DirReader - content_reader, # type: ContentReader + content_reader, # type: ContentsOf file_remover, # type: ExistingFileRemover version, # type: str - volumes, # type: Volumes + volumes, # type: VolumeOf ): self.volumes = volumes self.file_remover = file_remover @@ -47,23 +49,31 @@ errors = Errors(self.program_name, self.err) clock = Clock(self.now, errors) console = Console(self.program_name, self.out, self.err) - - self.actions = { - Action.empty: EmptyAction(clock, - self.file_remover, - self.volumes_listing, - self.file_reader, - self.volumes, - self.dir_reader, - self.content_reader, - console), - Action.print_version: PrintVersionAction(self.out, - self.version, - self.program_name), - Action.print_time: PrintTimeAction(self.out, clock), - } + self.empty_action = EmptyAction(clock, + self.file_remover, + self.volumes_listing, + self.file_reader, + self.volumes, + self.dir_reader, + self.content_reader, + console) + self.print_version_action = PrintVersionAction(self.out, + self.version) + self.print_time_action = PrintTimeAction(self.out, clock) def run_cmd(self, args, environ, uid): - parsed = self.parser.parse(is_input_interactive(), args) - self.actions[parsed.action].run_action(parsed, environ, uid) + args = self.parser.parse( + default_is_interactive=is_input_interactive(), + args=args, + argv0=self.argv0, + environ=environ, + uid=uid) + + if type(args) is PrintVersionArgs: + return self.print_version_action.run_action(args) + elif type(args) is EmptyActionArgs: + return self.empty_action.run_action(args) + elif type(args) is PrintTimeArgs: + return self.print_time_action.run_action(args) + return EX_OK diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/existing_file_remover.py trash-cli-0.23.11.10/trashcli/empty/existing_file_remover.py --- trash-cli-0.23.2.13.2/trashcli/empty/existing_file_remover.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/existing_file_remover.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,7 +1,5 @@ -from trashcli.fs import FileRemover +from trashcli.fs import RealRemoveFileIfExists, RealRemoveFile2 -class ExistingFileRemover: - @staticmethod - def remove_file_if_exists(path): - return FileRemover.remove_file_if_exists(path) +class ExistingFileRemover(RealRemoveFileIfExists, RealRemoveFile2): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/file_system_dir_reader.py trash-cli-0.23.11.10/trashcli/empty/file_system_dir_reader.py --- trash-cli-0.23.2.13.2/trashcli/empty/file_system_dir_reader.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/file_system_dir_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,9 @@ +from trashcli.fs import RealExists, RealEntriesIfDirExists +from trashcli.lib.dir_reader import DirReader + + +class FileSystemDirReader(DirReader, + RealEntriesIfDirExists, + RealExists, + ): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/guard.py trash-cli-0.23.11.10/trashcli/empty/guard.py --- trash-cli-0.23.2.13.2/trashcli/empty/guard.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/guard.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,11 +1,11 @@ from typing import Iterable, NamedTuple from trashcli.empty.user import User - +from trashcli.trash_dirs_scanner import TrashDir UserIntention = NamedTuple('UserIntention', [('ok_to_empty', bool), - ('trash_dirs', Iterable[str])]) + ('trash_dirs', Iterable[TrashDir])]) class Guard: @@ -13,16 +13,27 @@ def __init__(self, user): # type: (User) -> None self.user = user - def ask_the_user(self, parsed_interactive, trash_dirs - ): # type: (bool, Iterable[str]) -> UserIntention + def ask_the_user(self, + parsed_interactive, # type: bool + trash_dirs, # type: Iterable[TrashDir] + ): # type: (...) -> UserIntention if parsed_interactive: - trash_dirs_list = list(trash_dirs) - ok_to_empty = \ - self.user.do_you_wanna_empty_trash_dirs(trash_dirs_list) - list_result = trash_dirs_list if ok_to_empty else [] - return UserIntention(ok_to_empty=ok_to_empty, - trash_dirs=list_result) + return self._interactive(trash_dirs) else: - trash_dirs_list = trash_dirs - return UserIntention(ok_to_empty=True, - trash_dirs=trash_dirs_list) + return self.non_interactive(trash_dirs) + + def _interactive(self, trash_dirs, # type: Iterable[TrashDir] + ): # type: (...) -> UserIntention + trash_dirs_list = list(trash_dirs) # type: Iterable[TrashDir] + ok_to_empty = \ + self.user.do_you_wanna_empty_trash_dirs(trash_dirs_list) + list_result = trash_dirs_list if ok_to_empty else [] + return UserIntention(ok_to_empty=ok_to_empty, + trash_dirs=list_result) + + def non_interactive(self, + trash_dirs, # type: Iterable[TrashDir] + ): + trash_dirs_list = trash_dirs # type: Iterable[TrashDir] + return UserIntention(ok_to_empty=True, + trash_dirs=trash_dirs_list) diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/main.py trash-cli-0.23.11.10/trashcli/empty/main.py --- trash-cli-0.23.2.13.2/trashcli/empty/main.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/main.py 2023-11-10 07:15:04.000000000 +0000 @@ -3,27 +3,37 @@ import sys from datetime import datetime +from trashcli.compat import Protocol + from trashcli import trash from trashcli.empty.empty_cmd import EmptyCmd -from trashcli.fs import ( - FileSystemContentReader, - FileSystemDirReader, - TopTrashDirRulesFileSystemReader, -) -from trashcli.list_mount_points import os_mount_points - -from .. import fstab -from ..fstab import VolumesListing +from trashcli.fs import RealContentsOf, ContentsOf from .existing_file_remover import ExistingFileRemover +from .file_system_dir_reader import FileSystemDirReader +from .top_trash_dir_rules_file_system_reader import \ + RealTopTrashDirRulesReader +from ..fstab.volume_listing import RealVolumesListing +from ..fstab.volume_of import RealVolumeOf + + +class ContentReader(ContentsOf, Protocol): + pass def main(): - empty_cmd = EmptyCmd(argv0=sys.argv[0], out=sys.stdout, err=sys.stderr, - volumes_listing=VolumesListing(os_mount_points), + empty_cmd = EmptyCmd(argv0=sys.argv[0], + out=sys.stdout, + err=sys.stderr, + volumes_listing=RealVolumesListing(), now=datetime.now, - file_reader=TopTrashDirRulesFileSystemReader(), + file_reader=RealTopTrashDirRulesReader(), file_remover=ExistingFileRemover(), content_reader=FileSystemContentReader(), dir_reader=FileSystemDirReader(), - version=trash.version, volumes=fstab.volumes) + version=trash.version, + volumes=RealVolumeOf()) return empty_cmd.run_cmd(sys.argv[1:], os.environ, os.getuid()) + + +class FileSystemContentReader(ContentReader, RealContentsOf): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/parser.py trash-cli-0.23.11.10/trashcli/empty/parser.py --- trash-cli-0.23.2.13.2/trashcli/empty/parser.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/parser.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,45 +1,42 @@ import argparse -from typing import List, NamedTuple +from typing import List -from trashcli.empty.actions import Action +from trashcli.empty.empty_action import EmptyActionArgs +from trashcli.empty.print_time_action import PrintTimeArgs +from trashcli.lib.environ import Environ +from trashcli.lib.print_version import PrintVersionArgs from trashcli.shell_completion import TRASH_DIRS, add_argument_to -Parsed = NamedTuple('Parsed', - [('action', Action), - ('user_specified_trash_dirs', List[str]), - ('all_users', bool), - ('interactive', bool), - ('days', int), - ('dry_run', bool), - ('verbose', int), - ]) - - class Parser: - def parse(self, default_is_interactive, args): + def parse(self, + default_is_interactive, # type: bool + environ, # type: Environ + args, # type: List[str] + uid, # type: int + argv0, # type: str + ): parser = self.make_parser(default_is_interactive) namespace = parser.parse_args(args) - return Parsed( - action=self.action_from_params(namespace), - user_specified_trash_dirs=namespace.user_specified_trash_dirs, - all_users=namespace.all_users, - interactive=namespace.interactive, - days=namespace.days, - dry_run=namespace.dry_run, - verbose=namespace.verbose, - ) - - @staticmethod - def action_from_params(params): - if params.version: - return Action.print_version - elif params.print_time: - return Action.print_time + if namespace.version: + return PrintVersionArgs( + argv0=argv0, + ) + elif namespace.print_time: + return PrintTimeArgs(environ=environ) else: - return Action.empty + return EmptyActionArgs( + user_specified_trash_dirs=namespace.user_specified_trash_dirs, + all_users=namespace.all_users, + interactive=namespace.interactive, + days=namespace.days, + dry_run=namespace.dry_run, + verbose=namespace.verbose, + environ=environ, + uid=uid, + ) @staticmethod def make_parser(default_is_interactive): diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/print_time_action.py trash-cli-0.23.11.10/trashcli/empty/print_time_action.py --- trash-cli-0.23.2.13.2/trashcli/empty/print_time_action.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/print_time_action.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,4 +1,16 @@ -from trashcli.trash import println +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from __future__ import print_function + +from typing import NamedTuple + +from trashcli.lib.environ import Environ + + +class PrintTimeArgs( + NamedTuple('PrintTimeArgs', [ + ('environ', Environ), + ])): + pass class PrintTimeAction: @@ -6,7 +18,8 @@ self.out = out self.clock = clock - def run_action(self, _parsed, environ, _uid): - now_value = self.clock.get_now_value(environ) - println(self.out, - now_value.replace(microsecond=0).isoformat()) + def run_action(self, + args, # type: PrintTimeArgs + ): + now_value = self.clock.get_now_value(args.environ) + print(now_value.replace(microsecond=0).isoformat(), file=self.out) diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/print_version_action.py trash-cli-0.23.11.10/trashcli/empty/print_version_action.py --- trash-cli-0.23.2.13.2/trashcli/empty/print_version_action.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/print_version_action.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -from trashcli.trash import print_version - - -class PrintVersionAction: - def __init__(self, out, version, program_name): - self.out = out - self.version = version - self.program_name = program_name - - def run_action(self, _parsed, _environ, _uid): - print_version(self.out, self.program_name, self.version) diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/top_trash_dir_rules_file_system_reader.py trash-cli-0.23.11.10/trashcli/empty/top_trash_dir_rules_file_system_reader.py --- trash-cli-0.23.2.13.2/trashcli/empty/top_trash_dir_rules_file_system_reader.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/top_trash_dir_rules_file_system_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,11 @@ +from trashcli.fs import RealExists, RealIsStickyDir, RealIsSymLink +from trashcli.trash_dirs_scanner import TopTrashDirRules + + +class RealTopTrashDirRulesReader( + TopTrashDirRules.Reader, + RealExists, + RealIsStickyDir, + RealIsSymLink, +): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/empty/user.py trash-cli-0.23.11.10/trashcli/empty/user.py --- trash-cli-0.23.2.13.2/trashcli/empty/user.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/empty/user.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,9 +1,15 @@ +from trashcli.lib.my_input import Input + + class User: - def __init__(self, prepare_output_message, input, parse_reply): + def __init__(self, + prepare_output_message, + input, # type: Input + parse_reply): self.prepare_output_message = prepare_output_message self.input = input self.parse_reply = parse_reply def do_you_wanna_empty_trash_dirs(self, trash_dirs): - reply = self.input(self.prepare_output_message(trash_dirs)) + reply = self.input.read_input(self.prepare_output_message(trash_dirs)) return self.parse_reply(reply) diff -Nru trash-cli-0.23.2.13.2/trashcli/file_system_reader.py trash-cli-0.23.11.10/trashcli/file_system_reader.py --- trash-cli-0.23.2.13.2/trashcli/file_system_reader.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/file_system_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,14 @@ +from trashcli.fs import RealIsStickyDir, RealHasStickyBit, \ + RealIsSymLink, RealContentsOf, RealEntriesIfDirExists, RealExists +from trashcli.list.fs import FileSystemReaderForListCmd + + +class FileSystemReader(FileSystemReaderForListCmd, + RealIsStickyDir, + RealHasStickyBit, + RealIsSymLink, + RealContentsOf, + RealEntriesIfDirExists, + RealExists + ): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/fs.py trash-cli-0.23.11.10/trashcli/fs.py --- trash-cli-0.23.2.13.2/trashcli/fs.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,138 +1,259 @@ import os import shutil import stat +from abc import abstractmethod -from trashcli.empty.delete_according_date import ContentReader -from trashcli.trash import DirReader -from trashcli.trash_dirs_scanner import TopTrashDirRules +from typing import Iterable, List +from trashcli.compat import Protocol -class FileSystemDirReader(DirReader): - def entries_if_dir_exists(self, path): - if os.path.exists(path): - for entry in os.listdir(path): - yield entry +class FileSize(Protocol): + @abstractmethod + def file_size(self, path): + raise NotImplementedError() + + +class MakeFileExecutable(Protocol): + @abstractmethod + def make_file_executable(self, path): + raise NotImplementedError() + + +class WriteFile(Protocol): + @abstractmethod + def write_file(self, name, contents): + raise NotImplementedError() + + +class ReadFile(Protocol): + @abstractmethod + def read_file(self, path): + raise NotImplementedError() + + +class Move(Protocol): + @abstractmethod + def move(self, path, dest): + raise NotImplementedError() + + +class RemoveFile2(Protocol): + @abstractmethod + def remove_file2(self, path): + raise NotImplementedError() + +class RemoveFile(Protocol): + @abstractmethod + def remove_file(self, path): + raise NotImplementedError() + + +class RemoveFileIfExists(Protocol): + @abstractmethod + def remove_file_if_exists(self, path): + raise NotImplementedError() + + +class EntriesIfDirExists(Protocol): + @abstractmethod + def entries_if_dir_exists(self, path): # type: (str) -> List[str] + raise NotImplementedError() + + +class PathExists(Protocol): + @abstractmethod def exists(self, path): - return os.path.exists(path) + raise NotImplementedError() -class FileReader(TopTrashDirRules.Reader): - def exists(self, path): # type: (str) -> bool - return os.path.exists(path) +class HasStickyBit(Protocol): + @abstractmethod + def has_sticky_bit(self, path): # type: (str) -> bool + raise NotImplementedError + +class IsStickyDir(Protocol): + @abstractmethod def is_sticky_dir(self, path): # type: (str) -> bool - return FileSystemReader.is_sticky_dir(path) + raise NotImplementedError + +class IsSymLink(Protocol): + @abstractmethod def is_symlink(self, path): # type: (str) -> bool - return FileSystemReader.is_symlink(path) + raise NotImplementedError + +class ContentsOf(Protocol): + @abstractmethod + def contents_of(self, path): + raise NotImplementedError() + + +class RealEntriesIfDirExists(EntriesIfDirExists): + def entries_if_dir_exists(self, path): + if os.path.exists(path): + for entry in os.listdir(path): + yield entry -class TopTrashDirRulesFileSystemReader(TopTrashDirRules.Reader): + +class RealExists(PathExists): def exists(self, path): # type: (str) -> bool - return FileSystemReader().exists(path) + return os.path.exists(path) - def is_sticky_dir(self, path): # type: (str) -> bool - return FileSystemReader().is_sticky_dir(path) - def is_symlink(self, path): # type: (str) -> bool - return FileSystemReader().is_symlink(path) +class RealHasStickyBit(HasStickyBit): + def has_sticky_bit(self, path): + return (os.stat(path).st_mode & stat.S_ISVTX) == stat.S_ISVTX -class FileSystemReader(FileSystemDirReader): +class RealIsStickyDir(IsStickyDir, RealHasStickyBit): + def is_sticky_dir(self, path): # type: (str) -> bool + return os.path.isdir(path) and self.has_sticky_bit(path) - @staticmethod - def is_sticky_dir(path): # type: (str) -> bool - import os - return os.path.isdir(path) and has_sticky_bit(path) - @staticmethod - def is_symlink(path): # type: (str) -> bool +class RealIsSymLink(IsSymLink): + def is_symlink(self, path): # type: (str) -> bool return os.path.islink(path) - @staticmethod - def contents_of(path): - return read_file(path) - -class FileSystemContentReader(ContentReader): +class RealContentsOf(ContentsOf): def contents_of(self, path): - return read_file(path) + return _read_file(path) -is_sticky_dir = FileSystemReader().is_sticky_dir +class RealRemoveFile(RemoveFile): + def remove_file(self, path): + if os.path.lexists(path): + try: + os.remove(path) + except: + return shutil.rmtree(path) -class FileRemover: - @staticmethod - def remove_file(path): +class RealRemoveFile2(RemoveFile2): + def remove_file2(self, path): try: os.remove(path) except OSError: shutil.rmtree(path) - @classmethod - def remove_file_if_exists(cls, path): - if os.path.lexists(path): cls.remove_file(path) +class RealRemoveFileIfExists(RemoveFileIfExists, RemoveFile2): + def remove_file_if_exists(self, path): + if os.path.lexists(path): self.remove_file2(path) -def contents_of(path): # TODO remove - return FileSystemReader().contents_of(path) +class RealMove(Move): + def move(self, path, dest): + return shutil.move(path, str(dest)) -def has_sticky_bit(path): - import os - import stat - return (os.stat(path).st_mode & stat.S_ISVTX) == stat.S_ISVTX +class ListFilesInDir(Protocol): + @abstractmethod + def list_files_in_dir(self, path): # type: (str) -> Iterable[str] + raise NotImplementedError() -def remove_file(path): - if (os.path.lexists(path)): - try: - os.remove(path) - except: - return shutil.rmtree(path) +class RealListFilesInDir(ListFilesInDir): + def list_files_in_dir(self, path): # type: (str) -> Iterable[str] + for entry in os.listdir(path): + result = os.path.join(path, entry) + yield result -def move(path, dest): - return shutil.move(path, str(dest)) +class MkDirs(Protocol): + @abstractmethod + def mkdirs(self, path): + raise NotImplementedError() -def list_files_in_dir(path): - for entry in os.listdir(path): - result = os.path.join(path, entry) - yield result +class RealMkDirs(MkDirs): + def mkdirs(self, path): + if os.path.isdir(path): + return + os.makedirs(path) -def mkdirs(path): - if os.path.isdir(path): - return - os.makedirs(path) +class AtomicWrite(Protocol): + @abstractmethod + def atomic_write(self, path, content): + raise NotImplementedError() -def atomic_write(path, content): - file_handle = open_for_write_in_exclusive_and_create_mode(path) - os.write(file_handle, content) - os.close(file_handle) + @abstractmethod + def open_for_write_in_exclusive_and_create_mode(self, path): + raise NotImplementedError() -def open_for_write_in_exclusive_and_create_mode(path): - return os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) +class RealAtomicWrite(AtomicWrite): + def atomic_write(self, path, content): + file_handle = self.open_for_write_in_exclusive_and_create_mode(path) + os.write(file_handle, content) + os.close(file_handle) + def open_for_write_in_exclusive_and_create_mode(self, path): + return os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) -def read_file(path): - with open(path) as f: - return f.read() + +class RealReadFile(ReadFile): + def read_file(self, path): + return _read_file(path) + + +class RealWriteFile(WriteFile): + def write_file(self, name, contents): + with open(name, 'w') as f: + f.write(contents) -def write_file(name, contents): - with open(name, 'w') as f: - f.write(contents) +class RealMakeFileExecutable(MakeFileExecutable): + def make_file_executable(self, path): + os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR) -def make_file_executable(path): - os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR) +class RealFileSize(FileSize): + def file_size(self, path): + return os.stat(path).st_size + + +class FsMethods( + RealEntriesIfDirExists, + RealExists, + RealIsStickyDir, + RealIsSymLink, + RealContentsOf, + RealRemoveFile, + RealRemoveFile2, + RealRemoveFileIfExists, + RealMove, + RealListFilesInDir, + RealMkDirs, + RealAtomicWrite, + RealReadFile, + RealWriteFile, + RealMakeFileExecutable, + RealFileSize, +): + pass + + +def _read_file(path): + with open(path) as f: + return f.read() -def file_size(path): - return os.stat(path).st_size +has_sticky_bit = RealHasStickyBit().has_sticky_bit +contents_of = RealContentsOf().contents_of +remove_file = RealRemoveFile().remove_file +move = RealMove().move +list_files_in_dir = RealListFilesInDir().list_files_in_dir +mkdirs = RealMkDirs().mkdirs +atomic_write = RealAtomicWrite().atomic_write +open_for_write_in_exclusive_and_create_mode = RealAtomicWrite().open_for_write_in_exclusive_and_create_mode +read_file = RealReadFile().read_file +write_file = RealWriteFile().write_file +make_file_executable = RealMakeFileExecutable().make_file_executable +file_size = RealFileSize().file_size +remove_file2 = RealRemoveFile2().remove_file2 +is_sticky_dir = RealIsStickyDir().is_sticky_dir diff -Nru trash-cli-0.23.2.13.2/trashcli/fstab/mount_points_listing.py trash-cli-0.23.11.10/trashcli/fstab/mount_points_listing.py --- trash-cli-0.23.2.13.2/trashcli/fstab/mount_points_listing.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/fstab/mount_points_listing.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,65 @@ +# Copyright (C) 2009-2020 Andrea Francia Trivolzio(PV) Italy +import os +from abc import ABCMeta, abstractmethod + +import six + + +@six.add_metaclass(ABCMeta) +class MountPointsListing: + @abstractmethod + def list_mount_points(self): + raise NotImplementedError() + + +class RealMountPointsListing(MountPointsListing): + def list_mount_points(self): + return os_mount_points() + + +class FakeMountPointsListing(MountPointsListing): + def __init__(self, mount_points): + self.mount_points = mount_points + + def set_mount_points(self, mount_points): + self.mount_points = mount_points + + def list_mount_points(self): + return self.mount_points + + +def os_mount_points(): + import psutil + # List of accepted non-physical fstypes + fstypes = [ + 'nfs', + 'nfs4', + 'p9', # file system used in WSL 2 (Windows Subsystem for Linux) + 'btrfs', + 'fuse', # https://github.com/andreafrancia/trash-cli/issues/250 + 'fuse.glusterfs', + # https://github.com/andreafrancia/trash-cli/issues/255 + 'fuse.mergerfs', + ] + + # Append fstypes of physical devices to list + fstypes += set([p.fstype for p in psutil.disk_partitions()]) + + partitions = Partitions(fstypes) + + for p in psutil.disk_partitions(all=True): + if os.path.isdir(p.mountpoint) and \ + partitions.should_used_by_trashcli(p): + yield p.mountpoint + + +class Partitions: + def __init__(self, physical_fstypes): + self.physical_fstypes = physical_fstypes + + def should_used_by_trashcli(self, partition): + if ((partition.device, partition.mountpoint, + partition.fstype) == + ('tmpfs', '/tmp', 'tmpfs')): + return True + return partition.fstype in self.physical_fstypes diff -Nru trash-cli-0.23.2.13.2/trashcli/fstab/volume_listing.py trash-cli-0.23.11.10/trashcli/fstab/volume_listing.py --- trash-cli-0.23.2.13.2/trashcli/fstab/volume_listing.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/fstab/volume_listing.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,43 @@ +import os +from abc import ABCMeta, abstractmethod + +import six + +from trashcli.fstab.mount_points_listing import MountPointsListing, \ + RealMountPointsListing + + +@six.add_metaclass(ABCMeta) +class VolumesListing: + @abstractmethod + def list_volumes(self, environ): # type (dict) -> Iterable[str] + raise NotImplementedError() + + +class RealVolumesListing(VolumesListing): + def list_volumes(self, environ): + return VolumesListingImpl(RealMountPointsListing()).list_volumes(environ) + + +class VolumesListingImpl: + def __init__(self, + mount_points_listing, # type: MountPointsListing + ): + self.mount_points_listing = mount_points_listing + + def list_volumes(self, environ): + if 'TRASH_VOLUMES' in environ and environ['TRASH_VOLUMES']: + return [vol + for vol in environ['TRASH_VOLUMES'].split(':') + if vol != ''] + return self.mount_points_listing.list_mount_points() + + +class NoVolumesListing(VolumesListing): + def list_volumes(self, environ): + return [] + + +class RealIsMount: + def is_mount(self, path): + return os.path.ismount(path) diff -Nru trash-cli-0.23.2.13.2/trashcli/fstab/volume_of.py trash-cli-0.23.11.10/trashcli/fstab/volume_of.py --- trash-cli-0.23.2.13.2/trashcli/fstab/volume_of.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/fstab/volume_of.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,35 @@ +import os +from abc import ABCMeta, abstractmethod + +import six + +from trashcli.fstab.volume_listing import RealIsMount + + +@six.add_metaclass(ABCMeta) +class VolumeOf: + @abstractmethod + def volume_of(self, path): + raise NotImplementedError() + + +class RealVolumeOf(VolumeOf): + def __init__(self): + self.impl = VolumeOfImpl(RealIsMount(), os.path.abspath) + + def volume_of(self, path): + return self.impl.volume_of(path) + + +class VolumeOfImpl(VolumeOf): + def __init__(self, ismount, abspath): + self.ismount = ismount + self.abspath = abspath + + def volume_of(self, path): + path = self.abspath(path) + while path != os.path.dirname(path): + if self.ismount.is_mount(path): + break + path = os.path.dirname(path) + return path diff -Nru trash-cli-0.23.2.13.2/trashcli/fstab/volumes.py trash-cli-0.23.11.10/trashcli/fstab/volumes.py --- trash-cli-0.23.2.13.2/trashcli/fstab/volumes.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/fstab/volumes.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,73 @@ +from abc import ABCMeta + +import six +import os +from trashcli.fstab.mount_points_listing import MountPointsListing, \ + RealMountPointsListing +from trashcli.fstab.volume_of import VolumeOf, RealVolumeOf + + +@six.add_metaclass(ABCMeta) +class Volumes(VolumeOf, MountPointsListing): + pass + + +class RealVolumes(Volumes): + def volume_of(self, path): + return RealVolumeOf().volume_of(path) + + def list_mount_points(self): + return RealMountPointsListing().list_mount_points() + + +class VolumesImpl(Volumes): + def __init__(self, + volumes, # type: VolumeOf + mount_point_listing, # type: MountPointsListing + ): + self.volumes = volumes + self.mount_point_listing = mount_point_listing + + def volume_of(self, path): + return self.volumes.volume_of(path) + + def list_mount_points(self): + return self.mount_point_listing.list_mount_points() + + +class FakeVolumes(Volumes): + def __init__(self, + mount_points, # type Iterable[str] + ): + self.mount_points = mount_points + + def list_mount_points(self): + return self.mount_points + + def volume_of(self, path): + while path != os.path.dirname(path): + if self.is_a_mount_point(path): + break + path = os.path.dirname(path) + return path + + def is_a_mount_point(self, path): + return path in self.mount_points + + def add_volume(self, path): + self.mount_points.append(path) + + +class FakeVolumes2(Volumes): + def __init__(self, volume_of_string, volumes_list): + self.volume_of_string = volume_of_string + self.volumes_list = volumes_list + + def volume_of(self, path): + return self.volume_of_string % path + + def set_volumes(self, volumes_list): + self.volumes_list = volumes_list + + def list_mount_points(self): + return self.volumes_list diff -Nru trash-cli-0.23.2.13.2/trashcli/fstab.py trash-cli-0.23.11.10/trashcli/fstab.py --- trash-cli-0.23.2.13.2/trashcli/fstab.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/fstab.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,66 +0,0 @@ -import os - -from typing import List - - -def volume_of(path): - return volumes.volume_of(path) - - -def create_fake_volume_of(volumes): - return Volumes(FakeIsMount(volumes), os.path.normpath) - - -class RealIsMount: - def is_mount(self, path): - return os.path.ismount(path) - - -class FakeIsMount: - def __init__(self, - mount_points, # type: List[str] - ): - self.mount_points = mount_points - - def is_mount(self, path): - if path == '/': - return True - path = os.path.normpath(path) - if path in self.mount_points_list(): - return True - return False - - def mount_points_list(self): - return set(['/'] + self.mount_points) - - def add_mount_point(self, path): - self.mount_points.append(path) - - -class Volumes: - def __init__(self, ismount, abspath): - self.ismount = ismount - self.abspath = abspath - - def volume_of(self, path): - path = self.abspath(path) - while path != os.path.dirname(path): - if self.ismount.is_mount(path): - break - path = os.path.dirname(path) - return path - - -volumes = Volumes(RealIsMount(), os.path.abspath) - - -class VolumesListing: - def __init__(self, os_mount_points): - self.os_mount_points = os_mount_points - - def list_volumes(self, environ): - if 'TRASH_VOLUMES' in environ and environ['TRASH_VOLUMES']: - return [vol - for vol in environ['TRASH_VOLUMES'].split(':') - if vol != ''] - return self.os_mount_points() diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/dir_checker.py trash-cli-0.23.11.10/trashcli/lib/dir_checker.py --- trash-cli-0.23.2.13.2/trashcli/lib/dir_checker.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/dir_checker.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,9 @@ +from __future__ import absolute_import + +import os + + +class DirChecker: + @staticmethod + def is_dir(path): + return os.path.isdir(path) diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/dir_reader.py trash-cli-0.23.11.10/trashcli/lib/dir_reader.py --- trash-cli-0.23.2.13.2/trashcli/lib/dir_reader.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/dir_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,18 @@ +from __future__ import absolute_import + +from trashcli.compat import Protocol + +from trashcli.fs import EntriesIfDirExists, PathExists, RealEntriesIfDirExists, \ + RealExists + + +class DirReader( + EntriesIfDirExists, + PathExists, + Protocol, +): + pass + + +class RealDirReader(RealEntriesIfDirExists, RealExists): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/enum_repr.py trash-cli-0.23.11.10/trashcli/lib/enum_repr.py --- trash-cli-0.23.2.13.2/trashcli/lib/enum_repr.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/enum_repr.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,5 @@ +from enum import Enum + + +def repr_for_enum(enum): # type: (Enum) -> str + return "%s.%s" % (type(enum).__name__, enum.name) diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/environ.py trash-cli-0.23.11.10/trashcli/lib/environ.py --- trash-cli-0.23.2.13.2/trashcli/lib/environ.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/environ.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,13 @@ +import os + +from typing import Dict + +Environ = Dict[str, str] + + +def cast_environ(env, + ): # type: (...) -> Environ + if env == os.environ: + return env + else: + raise ValueError("env must be os.environ") diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/exit_codes.py trash-cli-0.23.11.10/trashcli/lib/exit_codes.py --- trash-cli-0.23.2.13.2/trashcli/lib/exit_codes.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/exit_codes.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,9 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from __future__ import absolute_import + +import os + +# Error codes (from os on *nix, hard coded for Windows): +EX_OK = getattr(os, 'EX_OK', 0) +EX_USAGE = getattr(os, 'EX_USAGE', 64) +EX_IOERR = getattr(os, 'EX_IOERR', 74) diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/logger.py trash-cli-0.23.11.10/trashcli/lib/logger.py --- trash-cli-0.23.2.13.2/trashcli/lib/logger.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/logger.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,6 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +import logging + +my_logger = logging.getLogger('trashcli.trash') +my_logger.setLevel(logging.WARNING) +my_logger.addHandler(logging.StreamHandler()) diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/my_input.py trash-cli-0.23.11.10/trashcli/lib/my_input.py --- trash-cli-0.23.2.13.2/trashcli/lib/my_input.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/my_input.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,5 +1,41 @@ -try: - my_input = raw_input # Python 2 -except NameError: - my_input = input # Python 3 -my_input = my_input +from abc import abstractmethod, ABCMeta + +import six +from six.moves import input as _my_input + + +@six.add_metaclass(ABCMeta) +class Input: + @abstractmethod + def read_input(self, prompt): # type: (str) -> str + raise NotImplementedError + + +class RealInput(Input): + def read_input(self, prompt): # type: (str) -> str + return _my_input(prompt) + + +class HardCodedInput(Input): + def __init__(self, reply=None): + self.reply, self.exception = self._reply(reply) + + def set_reply(self, reply): + self.reply, self.exception = self._reply(reply) + + def _reply(self, reply): + if reply is None: + return None, ValueError("No reply set") + else: + return reply, None + def raise_exception(self, exception): + self.exception = exception + + def read_input(self, prompt): # type: (str) -> str + self.used_prompt = prompt + if self.exception: + raise self.exception + return self.reply + + def last_prompt(self): + return self.used_prompt diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/my_permission_error.py trash-cli-0.23.11.10/trashcli/lib/my_permission_error.py --- trash-cli-0.23.2.13.2/trashcli/lib/my_permission_error.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/my_permission_error.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,5 +1,11 @@ -try: - MyPermissionError = PermissionError -except NameError: - MyPermissionError = OSError -MyPermissionError = MyPermissionError +from typing import Type + + +def get_permission_error_class(): # type: () -> Type[Exception] + try: + return PermissionError + except NameError: + return OSError + + +MyPermissionError = get_permission_error_class() diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/path_of_backup_copy.py trash-cli-0.23.11.10/trashcli/lib/path_of_backup_copy.py --- trash-cli-0.23.2.13.2/trashcli/lib/path_of_backup_copy.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/path_of_backup_copy.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,9 @@ +from __future__ import absolute_import + +import os + + +def path_of_backup_copy(trashinfo_path): + trash_dir = os.path.dirname(os.path.dirname(trashinfo_path)) + basename = os.path.basename(trashinfo_path)[:-len('.trashinfo')] + return os.path.join(trash_dir, 'files', basename) diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/print_version.py trash-cli-0.23.11.10/trashcli/lib/print_version.py --- trash-cli-0.23.2.13.2/trashcli/lib/print_version.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/print_version.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,33 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy + +from __future__ import absolute_import +from __future__ import print_function + +import os + +import six +from typing import NamedTuple + + +class PrintVersionArgs( + NamedTuple('PrintVersionArgs', [ + ('argv0', str), + ])): + + def program_name(self): + return os.path.basename(self.argv0) + + +class PrintVersionAction: + def __init__(self, out, version): + self.out = out + self.version = version + + def run_action(self, + args, # type: PrintVersionArgs + ): + print_version(self.out, args.program_name(), self.version) + + +def print_version(out, program_name, version): + print("%s %s" % (program_name, six.text_type(version)), file=out) diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/trash_dir_reader.py trash-cli-0.23.11.10/trashcli/lib/trash_dir_reader.py --- trash-cli-0.23.2.13.2/trashcli/lib/trash_dir_reader.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/trash_dir_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,28 @@ +from __future__ import absolute_import + +import os + +from trashcli.lib.dir_reader import DirReader + + +class TrashDirReader: + + def __init__(self, + dir_reader, # type: DirReader + ): + self.dir_reader = dir_reader + + def list_orphans(self, path): + info_dir = os.path.join(path, 'info') + files_dir = os.path.join(path, 'files') + for entry in self.dir_reader.entries_if_dir_exists(files_dir): + trashinfo_path = os.path.join(info_dir, entry + '.trashinfo') + file_path = os.path.join(files_dir, entry) + if not self.dir_reader.exists(trashinfo_path): + yield file_path + + def list_trashinfo(self, path): + info_dir = os.path.join(path, 'info') + for entry in self.dir_reader.entries_if_dir_exists(info_dir): + if entry.endswith('.trashinfo'): + yield os.path.join(info_dir, entry) diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/trash_dirs.py trash-cli-0.23.11.10/trashcli/lib/trash_dirs.py --- trash-cli-0.23.2.13.2/trashcli/lib/trash_dirs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/trash_dirs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,36 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from __future__ import absolute_import + +import os + +from trashcli.fstab.volume_of import VolumeOf + + +def home_trash_dir_path_from_env(environ): + if 'XDG_DATA_HOME' in environ: + return ['%(XDG_DATA_HOME)s/Trash' % environ] + elif 'HOME' in environ: + return ['%(HOME)s/.local/share/Trash' % environ] + return [] + + +def home_trash_dir_path_from_home(home_dir): + return '%s/.local/share/Trash' % home_dir + + +def home_trash_dir(environ, + volume_of, # type: VolumeOf + ): + paths = home_trash_dir_path_from_env(environ) + for path in paths: + yield path, volume_of.volume_of(path) + + +def volume_trash_dir1(volume, uid): + path = os.path.join(volume, '.Trash/%s' % uid) + yield path, volume + + +def volume_trash_dir2(volume, uid): + path = os.path.join(volume, ".Trash-%s" % uid) + yield path, volume diff -Nru trash-cli-0.23.2.13.2/trashcli/lib/user_info.py trash-cli-0.23.11.10/trashcli/lib/user_info.py --- trash-cli-0.23.2.13.2/trashcli/lib/user_info.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/lib/user_info.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,32 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from __future__ import absolute_import + +import pwd +from typing import Union + +from trashcli.lib.trash_dirs import ( + home_trash_dir_path_from_env, + home_trash_dir_path_from_home) + + +class UserInfo: + def __init__(self, home_trash_dir_paths, uid): + self.home_trash_dir_paths = home_trash_dir_paths + self.uid = uid + + +class SingleUserInfoProvider: + @staticmethod + def get_user_info(environ, uid): + return [UserInfo(home_trash_dir_path_from_env(environ), uid)] + + +class AllUsersInfoProvider: + @staticmethod + def get_user_info(_environ, _uid): + for user in pwd.getpwall(): + yield UserInfo([home_trash_dir_path_from_home(user.pw_dir)], + user.pw_uid) + + +UserInfoProvider = Union[SingleUserInfoProvider, AllUsersInfoProvider] diff -Nru trash-cli-0.23.2.13.2/trashcli/list/extractors.py trash-cli-0.23.11.10/trashcli/list/extractors.py --- trash-cli-0.23.2.13.2/trashcli/list/extractors.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/extractors.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,23 @@ +import os + +from trashcli.fs import file_size +from trashcli.lib.path_of_backup_copy import path_of_backup_copy +from trashcli.parse_trashinfo.maybe_parse_deletion_date import \ + maybe_parse_deletion_date + + +class DeletionDateExtractor: + def extract_attribute(self, _trashinfo_path, contents): + return maybe_parse_deletion_date(contents) + + +class SizeExtractor: + def extract_attribute(self, trashinfo_path, _contents): + backup_copy = path_of_backup_copy(trashinfo_path) + try: + return str(file_size(backup_copy)) + except FileNotFoundError: + if os.path.islink(backup_copy): + return 0 + else: + raise diff -Nru trash-cli-0.23.2.13.2/trashcli/list/fs.py trash-cli-0.23.11.10/trashcli/list/fs.py --- trash-cli-0.23.2.13.2/trashcli/list/fs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,18 @@ +import abc + +from six import add_metaclass + +from trashcli.fs import IsSymLink, ContentsOf, EntriesIfDirExists, PathExists, \ + IsStickyDir, HasStickyBit + + +@add_metaclass(abc.ABCMeta) +class FileSystemReaderForListCmd( + IsStickyDir, + HasStickyBit, + IsSymLink, + ContentsOf, + EntriesIfDirExists, + PathExists, +): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/list/list_trash_action.py trash-cli-0.23.11.10/trashcli/list/list_trash_action.py --- trash-cli-0.23.2.13.2/trashcli/list/list_trash_action.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/list_trash_action.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,170 @@ +from __future__ import print_function + +import os +from typing import List +from typing import NamedTuple + +from trashcli.lib.dir_reader import DirReader +from trashcli.lib.path_of_backup_copy import path_of_backup_copy +from trashcli.lib.trash_dir_reader import TrashDirReader +from trashcli.list.extractors import DeletionDateExtractor +from trashcli.list.extractors import SizeExtractor +from trashcli.parse_trashinfo.parse_path import parse_path +from trashcli.parse_trashinfo.parser_error import ParseError +from trashcli.trash_dirs_scanner import trash_dir_found +from trashcli.trash_dirs_scanner import \ + trash_dir_skipped_because_parent_is_symlink +from trashcli.trash_dirs_scanner import \ + trash_dir_skipped_because_parent_not_sticky + + +class ListTrashArgs( + NamedTuple('ListTrashArgs', [ + ('trash_dirs', List[str]), + ('attribute_to_print', str), + ('show_files', bool), + ('all_users', bool), + ])): + pass + + +class ListTrashAction: + def __init__(self, + environ, + uid, + selector, + out, + err, + dir_reader, + content_reader + ): + self.environ = environ + self.uid = uid + self.selector = selector + self.out = out + self.err = err + self.dir_reader = dir_reader + self.content_reader = content_reader + + def run_action(self, + args, # type: ListTrashArgs + ): + for message in ListTrash(self.environ, + self.uid, + self.selector, + self.dir_reader, + self.content_reader).list_all_trash(args): + self.print_event(message) + + def print_event(self, event): + if isinstance(event, Error): + print(event.error, file=self.err) + elif isinstance(event, Output): + print(event.message, file=self.out) + + +class ListTrash: + def __init__(self, + environ, + uid, + selector, + dir_reader, # type: DirReader + content_reader, + ): + self.environ = environ + self.uid = uid + self.selector = selector + self.dir_reader = dir_reader + self.content_reader = content_reader + + def list_all_trash(self, + args, # type: ListTrashArgs + ): + extractors = { + 'deletion_date': DeletionDateExtractor(), + 'size': SizeExtractor(), + } + user_specified_trash_dirs = args.trash_dirs + extractor = extractors[args.attribute_to_print] + show_files = args.show_files + all_users = args.all_users + trash_dirs = self.selector.select(all_users, + user_specified_trash_dirs, + self.environ, + self.uid) + for event, event_args in trash_dirs: + if event == trash_dir_found: + path, volume = event_args + trash_dir = TrashDirReader(self.dir_reader) + for trash_info in trash_dir.list_trashinfo(path): + for msg in self._print_trashinfo(volume, trash_info, + extractor, show_files): + yield msg + elif event == trash_dir_skipped_because_parent_not_sticky: + path, = event_args + msg = Error( + self.top_trashdir_skipped_because_parent_not_sticky(path)) + yield msg + elif event == trash_dir_skipped_because_parent_is_symlink: + path, = event_args + msg = Error( + self.top_trashdir_skipped_because_parent_is_symlink(path)) + yield msg + + def _print_trashinfo(self, + volume, + trashinfo_path, + extractor, + show_files): + try: + contents = self.content_reader.contents_of(trashinfo_path) + except IOError as e: + yield Error(str(e)) + else: + try: + relative_location = parse_path(contents) + except ParseError: + yield Error(self.print_parse_path_error(trashinfo_path)) + else: + attribute = extractor.extract_attribute(trashinfo_path, + contents) + original_location = os.path.join(volume, relative_location) + + if show_files: + original_file = path_of_backup_copy(trashinfo_path) + line = format_line2(attribute, original_location, + original_file) + else: + line = format_line(attribute, original_location) + yield Output(line) + + def top_trashdir_skipped_because_parent_is_symlink(self, trashdir): + return "TrashDir skipped because parent is symlink: %s" % trashdir + + def top_trashdir_skipped_because_parent_not_sticky(self, trashdir): + return "TrashDir skipped because parent not sticky: %s" % trashdir + + def print_parse_path_error(self, offending_file): + return "Parse Error: %s: Unable to parse Path." % offending_file + + +class Event: + pass + + +class Error(Event): + def __init__(self, error): + self.error = error + + +class Output(Event): + def __init__(self, message): + self.message = message + + +def format_line(attribute, original_location): + return "%s %s" % (attribute, original_location) + + +def format_line2(attribute, original_location, original_file): + return "%s %s -> %s" % (attribute, original_location, original_file) diff -Nru trash-cli-0.23.2.13.2/trashcli/list/main.py trash-cli-0.23.11.10/trashcli/list/main.py --- trash-cli-0.23.2.13.2/trashcli/list/main.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/main.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,91 @@ +# Copyright (C) 2011-2022 Andrea Francia Bereguardo(PV) Italy +import os +import sys + +import trashcli.trash +from trashcli.empty.main import ContentReader +from trashcli.file_system_reader import FileSystemReader +from trashcli.fs import RealContentsOf +from trashcli.fstab.volume_listing import RealVolumesListing +from trashcli.fstab.volume_of import RealVolumeOf +from trashcli.fstab.volume_of import VolumeOf +from trashcli.lib.dir_reader import DirReader, RealDirReader +from trashcli.lib.print_version import PrintVersionArgs, \ + PrintVersionAction +from trashcli.list.list_trash_action import ListTrashAction, ListTrashArgs +from trashcli.list.minor_actions.debug_volumes import DebugVolumes, \ + DebugVolumesArgs +from trashcli.list.minor_actions.list_trash_dirs import ListTrashDirs, \ + ListTrashDirsArgs +from trashcli.list.minor_actions.list_volumes import PrintVolumesList, \ + PrintVolumesArgs +from trashcli.list.minor_actions.print_python_executable import \ + PrintPythonExecutable, PrintPythonExecutableArgs +from trashcli.list.parser import Parser +from trashcli.list.trash_dir_selector import TrashDirsSelector +from trashcli.trash_dirs_scanner import TopTrashDirRules + + +def main(): + ListCmd( + out=sys.stdout, + err=sys.stderr, + environ=os.environ, + volumes_listing=RealVolumesListing(), + uid=os.getuid(), + volumes=RealVolumeOf(), + dir_reader=RealDirReader(), + file_reader=FileSystemReader(), + content_reader=RealContentsOf(), + version=trashcli.trash.version + ).run(sys.argv) + + +class ListCmd: + def __init__(self, + out, + err, + environ, + volumes_listing, + uid, + volumes, # type: VolumeOf + file_reader, # type: TopTrashDirRules.Reader + dir_reader, # type: DirReader + content_reader, # type: ContentReader + version, + ): + self.out = out + self.err = err + self.version = version + self.dir_reader = dir_reader + self.content_reader = content_reader + self.environ = environ + self.uid = uid + self.volumes_listing = volumes_listing + self.selector = TrashDirsSelector.make(volumes_listing, + file_reader, + volumes) + self.actions = {PrintVersionArgs: PrintVersionAction(self.out, + self.version), + PrintVolumesArgs: PrintVolumesList(self.environ, + self.volumes_listing), + DebugVolumesArgs: DebugVolumes(), + ListTrashDirsArgs: ListTrashDirs(self.environ, + self.uid, + self.selector), + ListTrashArgs: ListTrashAction(self.environ, + self.uid, + self.selector, + self.out, + self.err, + self.dir_reader, + self.content_reader), + PrintPythonExecutableArgs: PrintPythonExecutable()} + + def run(self, argv): + parser = Parser(os.path.basename(argv[0])) + args = parser.parse_list_args(argv[1:], argv[0]) + + action = self.actions[type(args)] + + action.run_action(args) diff -Nru trash-cli-0.23.2.13.2/trashcli/list/minor_actions/debug_volumes.py trash-cli-0.23.11.10/trashcli/list/minor_actions/debug_volumes.py --- trash-cli-0.23.2.13.2/trashcli/list/minor_actions/debug_volumes.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/minor_actions/debug_volumes.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,21 @@ +from pprint import pprint + +class DebugVolumesArgs: + pass + +class DebugVolumes: + def run_action(self, + _args, # type: DebugVolumesArgs + ): + import psutil + import os + all = sorted([p for p in psutil.disk_partitions(all=True)], + key=lambda p: p.device) + physical = sorted([p for p in psutil.disk_partitions()], + key=lambda p: p.device) + virtual = [p for p in all if p not in physical] + print("physical ->") + pprint(physical) + print("virtual ->") + pprint(virtual) + os.system('df -P') diff -Nru trash-cli-0.23.2.13.2/trashcli/list/minor_actions/list_trash_dirs.py trash-cli-0.23.11.10/trashcli/list/minor_actions/list_trash_dirs.py --- trash-cli-0.23.2.13.2/trashcli/list/minor_actions/list_trash_dirs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/minor_actions/list_trash_dirs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,38 @@ +from typing import List, NamedTuple + +from trashcli.trash_dirs_scanner import trash_dir_found, \ + trash_dir_skipped_because_parent_not_sticky, \ + trash_dir_skipped_because_parent_is_symlink + + +class ListTrashDirsArgs( + NamedTuple('ListTrashDirsArgs', + [('trash_dirs', List[str]), + ('all_users', bool)]) +): + pass + + +class ListTrashDirs: + def __init__(self, environ, uid, selector): + self.environ = environ + self.uid = uid + self.selector = selector + + def run_action(self, args): + user_specified_trash_dirs = args.trash_dirs + all_users = args.all_users + trash_dirs = self.selector.select(all_users, + user_specified_trash_dirs, + self.environ, + self.uid) + for event, event_args in trash_dirs: + if event == trash_dir_found: + path, volume = event_args + print("%s" % path) + elif event == trash_dir_skipped_because_parent_not_sticky: + path = event_args + print("parent_not_sticky: %s" % (path)) + elif event == trash_dir_skipped_because_parent_is_symlink: + path = event_args + print("parent_is_symlink: %s" % (path)) diff -Nru trash-cli-0.23.2.13.2/trashcli/list/minor_actions/list_volumes.py trash-cli-0.23.11.10/trashcli/list/minor_actions/list_volumes.py --- trash-cli-0.23.2.13.2/trashcli/list/minor_actions/list_volumes.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/minor_actions/list_volumes.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,14 @@ +class PrintVolumesArgs: + pass + + +class PrintVolumesList: + def __init__(self, environ, volumes_listing): + self.environ = environ + self.volumes_listing = volumes_listing + + def exectute(self, + args, # type: PrintVolumesArgs + ): + for volume in self.volumes_listing.list_volumes(self.environ): + print(volume) diff -Nru trash-cli-0.23.2.13.2/trashcli/list/minor_actions/print_python_executable.py trash-cli-0.23.11.10/trashcli/list/minor_actions/print_python_executable.py --- trash-cli-0.23.2.13.2/trashcli/list/minor_actions/print_python_executable.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/minor_actions/print_python_executable.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,10 @@ +class PrintPythonExecutableArgs: + pass + + +class PrintPythonExecutable: + def run_action(self, + _args, # type: PrintPythonExecutableArgs + ): + import sys + print(sys.executable) diff -Nru trash-cli-0.23.2.13.2/trashcli/list/parser.py trash-cli-0.23.11.10/trashcli/list/parser.py --- trash-cli-0.23.2.13.2/trashcli/list/parser.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/parser.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,115 @@ +import argparse +from enum import Enum +from typing import List, Union + +from trashcli.lib.print_version import PrintVersionArgs +from trashcli.list.list_trash_action import ListTrashArgs +from trashcli.list.minor_actions.debug_volumes import DebugVolumesArgs +from trashcli.list.minor_actions.list_trash_dirs import ListTrashDirsArgs +from trashcli.list.minor_actions.list_volumes import PrintVolumesArgs +from trashcli.list.minor_actions.print_python_executable import \ + PrintPythonExecutableArgs +from trashcli.shell_completion import add_argument_to, TRASH_DIRS + +Args = Union[ + PrintVersionArgs, + PrintVolumesArgs, + DebugVolumesArgs, + ListTrashDirsArgs, + ListTrashArgs, + PrintPythonExecutableArgs +] + + +class Parser: + def __init__(self, prog): + self.parser = argparse.ArgumentParser(prog=prog, + description='List trashed files', + epilog='Report bugs to https://github.com/andreafrancia/trash-cli/issues') + add_argument_to(self.parser) + self.parser.add_argument('--version', + dest='action', + action='store_const', + const=ListAction.print_version, + default=ListAction.list_trash, + help="show program's version number and exit") + self.parser.add_argument('--debug-volumes', + dest='action', + action='store_const', + const=ListAction.debug_volumes, + help=argparse.SUPPRESS) + self.parser.add_argument('--volumes', + dest='action', + action='store_const', + const=ListAction.list_volumes, + help="list volumes") + self.parser.add_argument('--trash-dirs', + dest='action', + action='store_const', + const=ListAction.list_trash_dirs, + help="list trash dirs") + self.parser.add_argument('--trash-dir', + action='append', + default=[], + dest='trash_dirs', + help='specify the trash directory to use' + ).complete = TRASH_DIRS + self.parser.add_argument('--size', + action='store_const', + default='deletion_date', + const='size', + dest='attribute_to_print', + help=argparse.SUPPRESS) + self.parser.add_argument('--files', + action='store_true', + default=False, + dest='show_files', + help=argparse.SUPPRESS) + self.parser.add_argument('--all-users', + action='store_true', + dest='all_users', + help='list trashcans of all the users') + self.parser.add_argument('--python', + dest='action', + action='store_const', + const=ListAction.print_python_executable, + help=argparse.SUPPRESS) + + def parse_list_args(self, + args, # type: List[str] + argv0, # type: str + ): # type: (...) -> Args + + parsed = self.parser.parse_args(args) + + if parsed.action == ListAction.print_version: + return PrintVersionArgs(argv0=argv0) + if parsed.action == ListAction.list_volumes: + return PrintVolumesArgs() + if parsed.action == ListAction.debug_volumes: + return DebugVolumesArgs() + if parsed.action == ListAction.list_trash_dirs: + return ListTrashDirsArgs( + trash_dirs=parsed.trash_dirs, + all_users=parsed.all_users + ) + if parsed.action == ListAction.list_trash: + return ListTrashArgs( + trash_dirs=parsed.trash_dirs, + attribute_to_print=parsed.attribute_to_print, + show_files=parsed.show_files, + all_users=parsed.all_users + ) + if parsed.action == ListAction.print_python_executable: + return PrintPythonExecutableArgs() + + raise ValueError('Unknown action: {}'.format(parsed.action)) + + +class ListAction(Enum): + debug_volumes = 'debug_volumes' + print_version = 'print_version' + list_trash = 'list_trash' + list_volumes = 'list_volumes' + list_trash_dirs = 'list_trash_dirs' + print_python_executable = 'print_python_executable' diff -Nru trash-cli-0.23.2.13.2/trashcli/list/trash_dir_selector.py trash-cli-0.23.11.10/trashcli/list/trash_dir_selector.py --- trash-cli-0.23.2.13.2/trashcli/list/trash_dir_selector.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list/trash_dir_selector.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,56 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from typing import List, Dict, Iterator, Tuple + +from trashcli.fstab.volume_of import VolumeOf +from trashcli.lib.dir_checker import DirChecker +from trashcli.lib.user_info import AllUsersInfoProvider, \ + SingleUserInfoProvider +from trashcli.trash_dirs_scanner import trash_dir_found, TrashDir, \ + TopTrashDirRules, TrashDirsScanner + + +class TrashDirsSelector: + def __init__(self, + current_user_dirs, + all_users_dirs, + volumes # type: VolumeOf + ): + self.current_user_dirs = current_user_dirs + self.all_users_dirs = all_users_dirs + self.volumes = volumes + + def select(self, + all_users_flag, # type: bool + user_specified_dirs, # type: List[str] + environ, # type: Dict[str, str] + uid, # type: int + ): # type: (...) -> Iterator[Tuple[str, TrashDir]] + if all_users_flag: + for dir in self.all_users_dirs.scan_trash_dirs(environ, uid): + yield dir + else: + if not user_specified_dirs: + for dir in self.current_user_dirs.scan_trash_dirs(environ, uid): + yield dir + for dir in user_specified_dirs: + yield trash_dir_found, ( + TrashDir(dir, self.volumes.volume_of(dir))) + + @staticmethod + def make(volumes_listing, + reader, # type: TopTrashDirRules.Reader + volumes # type: VolumeOf + ): + user_info_provider = SingleUserInfoProvider() + user_dir_scanner = TrashDirsScanner(user_info_provider, + volumes_listing, + TopTrashDirRules(reader), + DirChecker()) + all_users_info_provider = AllUsersInfoProvider() + all_users_scanner = TrashDirsScanner(all_users_info_provider, + volumes_listing, + TopTrashDirRules(reader), + DirChecker()) + return TrashDirsSelector(user_dir_scanner, + all_users_scanner, + volumes) diff -Nru trash-cli-0.23.2.13.2/trashcli/list.py trash-cli-0.23.11.10/trashcli/list.py --- trash-cli-0.23.2.13.2/trashcli/list.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,363 +0,0 @@ -# Copyright (C) 2011-2022 Andrea Francia Bereguardo(PV) Italy -import argparse -import os -import sys -from pprint import pprint - -from trashcli.list_mount_points import os_mount_points -from trashcli.shell_completion import TRASH_DIRS, add_argument_to -from trashcli.super_enum import SuperEnum - -from . import fstab -from .fs import FileSystemReader, file_size -from .fstab import Volumes, VolumesListing -from .trash import ( - AllUsersInfoProvider, - DirChecker, - ParseError, - TrashDirReader, - UserInfoProvider, - maybe_parse_deletion_date, - parse_path, - path_of_backup_copy, - print_version, - version, -) -from .trash_dirs_scanner import ( - TopTrashDirRules, - TrashDir, - TrashDirsScanner, - trash_dir_found, - trash_dir_skipped_because_parent_is_symlink, - trash_dir_skipped_because_parent_not_sticky, -) - - -def main(): - ListCmd( - out=sys.stdout, - err=sys.stderr, - environ=os.environ, - uid=os.getuid(), - volumes_listing=VolumesListing(os_mount_points), - volumes=fstab.volumes - ).run(sys.argv) - - -class ListCmd: - def __init__(self, - out, - err, - environ, - volumes_listing, - uid, - volumes, # type: Volumes - file_reader=FileSystemReader(), - version=version, - ): - - self.out = out - self.output = ListCmdOutput(out, err) - self.err = self.output.err - self.version = version - self.file_reader = file_reader - self.environ = environ - self.uid = uid - self.volumes_listing = volumes_listing - self.selector = TrashDirsSelector.make(volumes_listing, - file_reader, - volumes) - - def run(self, argv): - parser = Parser(os.path.basename(argv[0])) - parsed = parser.parse_list_args(argv[1:]) - if parsed.action == Action.print_version: - print_version(self.out, argv[0], self.version) - elif parsed.action == Action.list_volumes: - self.print_volumes_list() - elif parsed.action == Action.debug_volumes: - self.debug_volumes() - elif parsed.action == Action.list_trash_dirs: - self.list_trash_dirs(parsed.trash_dirs, - parsed.all_users, - self.environ, - self.uid) - elif parsed.action == Action.list_trash: - extractor = { - 'deletion_date': DeletionDateExtractor(), - 'size': SizeExtractor(), - }[parsed.attribute_to_print] - self.list_trash(parsed.trash_dirs, - extractor, - parsed.show_files, - parsed.all_users, - self.environ, - self.uid) - elif parsed.action == Action.print_python_executable: - self.print_python_executable() - else: - raise ValueError('Unknown action: ' + parsed.action) - - def debug_volumes(self): - import psutil - import os - all = sorted([p for p in psutil.disk_partitions(all=True)], - key=lambda p: p.device) - physical = sorted([p for p in psutil.disk_partitions()], - key=lambda p: p.device) - virtual = [p for p in all if p not in physical] - print("physical ->") - pprint(physical) - print("virtual ->") - pprint(virtual) - os.system('df -P') - - def list_trash(self, - user_specified_trash_dirs, - extractor, - show_files, - all_users, - environ, - uid): - trash_dirs = self.selector.select(all_users, - user_specified_trash_dirs, - environ, - uid) - for event, args in trash_dirs: - if event == trash_dir_found: - path, volume = args - trash_dir = TrashDirReader(self.file_reader) - for trash_info in trash_dir.list_trashinfo(path): - self._print_trashinfo(volume, trash_info, extractor, - show_files) - elif event == trash_dir_skipped_because_parent_not_sticky: - path, = args - self.output.top_trashdir_skipped_because_parent_not_sticky(path) - elif event == trash_dir_skipped_because_parent_is_symlink: - path, = args - self.output.top_trashdir_skipped_because_parent_is_symlink(path) - - def list_trash_dirs(self, - user_specified_trash_dirs, - all_users, - environ, - uid): - trash_dirs = self.selector.select(all_users, - user_specified_trash_dirs, - environ, - uid) - for event, args in trash_dirs: - if event == trash_dir_found: - path, volume = args - print("%s" % path) - elif event == trash_dir_skipped_because_parent_not_sticky: - path = args - print("parent_not_sticky: %s" % (path)) - elif event == trash_dir_skipped_because_parent_is_symlink: - path = args - print("parent_is_symlink: %s" % (path)) - - def print_python_executable(self): - import sys - print(sys.executable) - - def _print_trashinfo(self, volume, trashinfo_path, extractor, show_files): - try: - contents = self.file_reader.contents_of(trashinfo_path) - except IOError as e: - self.output.print_read_error(e) - else: - try: - relative_location = parse_path(contents) - except ParseError: - self.output.print_parse_path_error(trashinfo_path) - else: - attribute = extractor.extract_attribute(trashinfo_path, - contents) - original_location = os.path.join(volume, relative_location) - - if show_files: - original_file = path_of_backup_copy(trashinfo_path) - line = format_line2(attribute, original_location, - original_file) - else: - line = format_line(attribute, original_location) - self.output.println(line) - - def print_volumes_list(self): - for volume in self.volumes_listing.list_volumes(self.environ): - print(volume) - - -def format_line(attribute, original_location): - return "%s %s" % (attribute, original_location) - - -def format_line2(attribute, original_location, original_file): - return "%s %s -> %s" % (attribute, original_location, original_file) - - -class DeletionDateExtractor: - def extract_attribute(self, _trashinfo_path, contents): - return maybe_parse_deletion_date(contents) - - -class SizeExtractor: - def extract_attribute(self, trashinfo_path, _contents): - backup_copy = path_of_backup_copy(trashinfo_path) - try: - return str(file_size(backup_copy)) - except FileNotFoundError: - if os.path.islink(backup_copy): - return 0 - else: - raise - - -def description(program_name, printer): - printer.usage('Usage: %s [OPTIONS...]' % program_name) - printer.summary('List trashed files') - printer.options( - " --version show program's version number and exit", - " -h, --help show this help message and exit") - printer.bug_reporting() - - -class TrashDirsSelector: - def __init__(self, - current_user_dirs, - all_users_dirs, - volumes # type: Volumes - ): - self.current_user_dirs = current_user_dirs - self.all_users_dirs = all_users_dirs - self.volumes = volumes - - def select(self, - all_users_flag, - user_specified_dirs, - environ, - uid): # type (bool, List[str], Dict[str, str], int) -> Iterator[Tuple[str, TrashDir[str, str]]] - if all_users_flag: - for dir in self.all_users_dirs.scan_trash_dirs(environ, uid): - yield dir - else: - if not user_specified_dirs: - for dir in self.current_user_dirs.scan_trash_dirs(environ, uid): - yield dir - for dir in user_specified_dirs: - yield trash_dir_found, ( - TrashDir(dir, self.volumes.volume_of(dir))) - - @staticmethod - def make(volumes_listing, - reader, # type: TopTrashDirRules.Reader - volumes # type: Volumes - ): - user_info_provider = UserInfoProvider() - user_dir_scanner = TrashDirsScanner(user_info_provider, - volumes_listing, - TopTrashDirRules(reader), - DirChecker()) - all_users_info_provider = AllUsersInfoProvider() - all_users_scanner = TrashDirsScanner(all_users_info_provider, - volumes_listing, - TopTrashDirRules(reader), - DirChecker()) - return TrashDirsSelector(user_dir_scanner, - all_users_scanner, - volumes) - - -class Action(SuperEnum): - debug_volumes = 'debug_volumes' - print_version = 'print_version' - list_trash = 'list_trash' - list_volumes = 'list_volumes' - list_trash_dirs = 'list_trash_dirs' - print_python_executable = 'print_python_executable' - - -class Parser: - def __init__(self, prog): - self.parser = argparse.ArgumentParser(prog=prog, - description='List trashed files', - epilog='Report bugs to https://github.com/andreafrancia/trash-cli/issues') - add_argument_to(self.parser) - self.parser.add_argument('--version', - dest='action', - action='store_const', - const=Action.print_version, - default=Action.list_trash, - help="show program's version number and exit") - self.parser.add_argument('--debug-volumes', - dest='action', - action='store_const', - const=Action.debug_volumes, - help=argparse.SUPPRESS) - self.parser.add_argument('--volumes', - dest='action', - action='store_const', - const=Action.list_volumes, - help="list volumes") - self.parser.add_argument('--trash-dirs', - dest='action', - action='store_const', - const=Action.list_trash_dirs, - help="list trash dirs") - self.parser.add_argument('--trash-dir', - action='append', - default=[], - dest='trash_dirs', - help='specify the trash directory to use' - ).complete = TRASH_DIRS - self.parser.add_argument('--size', - action='store_const', - default='deletion_date', - const='size', - dest='attribute_to_print', - help=argparse.SUPPRESS) - self.parser.add_argument('--files', - action='store_true', - default=False, - dest='show_files', - help=argparse.SUPPRESS) - self.parser.add_argument('--all-users', - action='store_true', - dest='all_users', - help='list trashcans of all the users') - self.parser.add_argument('--python', - dest='action', - action='store_const', - const=Action.print_python_executable, - help=argparse.SUPPRESS) - - def parse_list_args(self, args): - parsed = self.parser.parse_args(args) - return parsed - - -class ListCmdOutput: - def __init__(self, out, err): - self.out = out - self.err = err - - def println(self, line): - self.out.write(line + '\n') - - def error(self, line): - self.err.write(line + '\n') - - def print_read_error(self, error): - self.error(str(error)) - - def print_parse_path_error(self, offending_file): - self.error("Parse Error: %s: Unable to parse Path." % (offending_file)) - - def top_trashdir_skipped_because_parent_not_sticky(self, trashdir): - self.error("TrashDir skipped because parent not sticky: %s" - % trashdir) - - def top_trashdir_skipped_because_parent_is_symlink(self, trashdir): - self.error("TrashDir skipped because parent is symlink: %s" - % trashdir) diff -Nru trash-cli-0.23.2.13.2/trashcli/list_mount_points.py trash-cli-0.23.11.10/trashcli/list_mount_points.py --- trash-cli-0.23.2.13.2/trashcli/list_mount_points.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/list_mount_points.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -# Copyright (C) 2009-2020 Andrea Francia Trivolzio(PV) Italy -import os.path - - -def os_mount_points(): - import psutil - # List of accepted non-physical fstypes - fstypes = [ - 'nfs', - 'nfs4', - 'p9', # file system used in WSL 2 (Windows Subsystem for Linux) - 'btrfs', - 'fuse', # https://github.com/andreafrancia/trash-cli/issues/250 - 'fuse.glusterfs', #https://github.com/andreafrancia/trash-cli/issues/255 - 'fuse.mergerfs', - ] - - # Append fstypes of physical devices to list - fstypes += set([p.fstype for p in psutil.disk_partitions()]) - - partitions = Partitions(fstypes) - - for p in psutil.disk_partitions(all=True): - if os.path.isdir(p.mountpoint) and \ - partitions.should_used_by_trashcli(p): - yield p.mountpoint - - -class Partitions: - def __init__(self, physical_fstypes): - self.physical_fstypes = physical_fstypes - - def should_used_by_trashcli(self, partition): - if ((partition.device, partition.mountpoint, - partition.fstype) == - ('tmpfs', '/tmp', 'tmpfs')): - return True - return partition.fstype in self.physical_fstypes diff -Nru trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/basket.py trash-cli-0.23.11.10/trashcli/parse_trashinfo/basket.py --- trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/basket.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/parse_trashinfo/basket.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,9 @@ +from __future__ import absolute_import + + +class Basket: + def __init__(self, initial_value=None): + self.collected = initial_value + + def collect(self, value): + self.collected = value diff -Nru trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/maybe_parse_deletion_date.py trash-cli-0.23.11.10/trashcli/parse_trashinfo/maybe_parse_deletion_date.py --- trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/maybe_parse_deletion_date.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/parse_trashinfo/maybe_parse_deletion_date.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,18 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from __future__ import absolute_import + +from trashcli.parse_trashinfo.basket import Basket +from trashcli.parse_trashinfo.parse_trashinfo import ParseTrashInfo + + +def maybe_parse_deletion_date(contents): + result = Basket(unknown_date) + parser = ParseTrashInfo( + on_deletion_date=lambda date: result.collect(date), + on_invalid_date=lambda: result.collect(unknown_date) + ) + parser.parse_trashinfo(contents) + return result.collected + + +unknown_date = '????-??-?? ??:??:??' diff -Nru trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_deletion_date.py trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_deletion_date.py --- trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_deletion_date.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_deletion_date.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,11 @@ +from __future__ import absolute_import + +from trashcli.parse_trashinfo.parse_trashinfo import ParseTrashInfo +from trashcli.parse_trashinfo.basket import Basket + + +def parse_deletion_date(contents): + result = Basket() + parser = ParseTrashInfo(on_deletion_date=result.collect) + parser.parse_trashinfo(contents) + return result.collected diff -Nru trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_original_location.py trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_original_location.py --- trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_original_location.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_original_location.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,10 @@ +from __future__ import absolute_import + +import os + +from trashcli.parse_trashinfo.parse_path import parse_path + + +def parse_original_location(contents, volume_path): + path = parse_path(contents) + return os.path.join(volume_path, path) diff -Nru trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_path.py trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_path.py --- trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_path.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_path.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,12 @@ +from __future__ import absolute_import + +from six.moves.urllib.parse import unquote + +from trashcli.parse_trashinfo.parser_error import ParseError + + +def parse_path(contents): + for line in contents.split('\n'): + if line.startswith('Path='): + return unquote(line[len('Path='):]) + raise ParseError('Unable to parse Path') diff -Nru trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_trashinfo.py trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_trashinfo.py --- trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parse_trashinfo.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/parse_trashinfo/parse_trashinfo.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,34 @@ +from __future__ import absolute_import + +import datetime +from six.moves.urllib.parse import unquote + + +def do_nothing(*argv, **argvk): pass + + +class ParseTrashInfo: + def __init__(self, + on_deletion_date=do_nothing, + on_invalid_date=do_nothing, + on_path=do_nothing): + self.found_deletion_date = on_deletion_date + self.found_invalid_date = on_invalid_date + self.found_path = on_path + + def parse_trashinfo(self, contents): + found_deletion_date = False + for line in contents.split('\n'): + if not found_deletion_date and line.startswith('DeletionDate='): + found_deletion_date = True + try: + date = datetime.datetime.strptime( + line, "DeletionDate=%Y-%m-%dT%H:%M:%S") + except ValueError: + self.found_invalid_date() + else: + self.found_deletion_date(date) + + if line.startswith('Path='): + path = unquote(line[len('Path='):]) + self.found_path(path) diff -Nru trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parser_error.py trash-cli-0.23.11.10/trashcli/parse_trashinfo/parser_error.py --- trash-cli-0.23.2.13.2/trashcli/parse_trashinfo/parser_error.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/parse_trashinfo/parser_error.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,4 @@ +from __future__ import absolute_import + + +class ParseError(ValueError): pass diff -Nru trash-cli-0.23.2.13.2/trashcli/put/candidate.py trash-cli-0.23.11.10/trashcli/put/candidate.py --- trash-cli-0.23.2.13.2/trashcli/put/candidate.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/candidate.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -import os -import posixpath -import re - -from trashcli.put.path_maker import PathMakerType -from typing import NamedTuple, Type - -from trashcli.put.gate import Gate -from trashcli.put.security_check import CheckType - - -class Candidate(NamedTuple('Candidate', [ - ('trash_dir_path', str), - ('volume', str), - ('path_maker_type', PathMakerType), - ('check_type', CheckType), - ('gate', Type[Gate]), -])): - def info_dir(self): - return os.path.join(self.trash_dir_path, 'info') - - def files_dir(self): - return os.path.join(self.trash_dir_path, 'files') - - def norm_path(self): - return os.path.normpath(self.trash_dir_path) - - def shrink_user(self, environ): - path = self.norm_path() - if environ.get('TRASH_PUT_DISABLE_SHRINK', '') == '1': - return path - home_dir = environ.get('HOME', '') - home_dir = posixpath.normpath(home_dir) - if home_dir != '': - path = re.sub('^' + re.escape(home_dir + os.path.sep), - '~' + os.path.sep, path) - return path diff -Nru trash-cli-0.23.2.13.2/trashcli/put/class_name_meta.py trash-cli-0.23.11.10/trashcli/put/class_name_meta.py --- trash-cli-0.23.2.13.2/trashcli/put/class_name_meta.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/class_name_meta.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -class ClassNameMeta(type): - def __repr__(cls): - return cls.__name__ diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/candidate.py trash-cli-0.23.11.10/trashcli/put/core/candidate.py --- trash-cli-0.23.2.13.2/trashcli/put/core/candidate.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/candidate.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,39 @@ +import os +import posixpath +import re +from typing import NamedTuple + +from trashcli.put.core.check_type import CheckType +from trashcli.put.core.path_maker_type import PathMakerType +from trashcli.put.gate import Gate + + +class Candidate(NamedTuple('Candidate', [ + ('trash_dir_path', str), + ('volume', str), + ('path_maker_type', PathMakerType), + ('check_type', CheckType), + ('gate', Gate), +])): + def parent_dir(self): + return os.path.dirname(self.trash_dir_path) + + def info_dir(self): + return os.path.join(self.trash_dir_path, 'info') + + def files_dir(self): + return os.path.join(self.trash_dir_path, 'files') + + def norm_path(self): + return os.path.normpath(self.trash_dir_path) + + def shrink_user(self, environ): + path = self.norm_path() + if environ.get('TRASH_PUT_DISABLE_SHRINK', '') == '1': + return path + home_dir = environ.get('HOME', '') + home_dir = posixpath.normpath(home_dir) + if home_dir != '': + path = re.sub('^' + re.escape(home_dir + os.path.sep), + '~' + os.path.sep, path) + return path diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/check_type.py trash-cli-0.23.11.10/trashcli/put/core/check_type.py --- trash-cli-0.23.2.13.2/trashcli/put/core/check_type.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/check_type.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,10 @@ +from enum import Enum + + +class CheckType(Enum): + NoCheck = 'NoCheck' + TopTrashDirCheck = 'TopTrashDirCheck' + + +NoCheck = CheckType.NoCheck +TopTrashDirCheck = CheckType.TopTrashDirCheck diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/either.py trash-cli-0.23.11.10/trashcli/put/core/either.py --- trash-cli-0.23.2.13.2/trashcli/put/core/either.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/either.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,65 @@ +from abc import abstractmethod +from typing import Callable, TypeVar, Generic + +S = TypeVar("S") +R = TypeVar("R") +E = TypeVar("E") + + +class Either(Generic[S, E]): + @abstractmethod + def bind(self, + func): # type: (Callable[[S], Either[R, E]]) -> Either[R, E] + raise NotImplementedError + + @abstractmethod + def __eq__(self, other): # type: (object) -> bool + raise NotImplementedError + + def is_error(self): # type: () -> bool + return not self.is_valid() + + def is_valid(self): # type: () -> bool + return {Left: False, Right: True}[type(self)] + + def error(self): # type: () -> E + if isinstance(self, Left): + return self._error + else: + raise ValueError("Not an error: %s" % self) + + def value(self): + if isinstance(self, Right): + return self._value + else: + raise ValueError("Not a value: %s" % self) + + +class Right(Either[S, E]): + def __init__(self, value): # type: (S) -> None + self._value = value + + def bind(self, + func): # type: (Callable[[S], Either[R, E]]) -> Either[R, E] + return func(self._value) + + def __eq__(self, other): # type: (object) -> bool + return isinstance(other, Right) and self._value == other._value + + def __str__(self): # type: () -> str + return "Right %s" % self._value + + +class Left(Either[S, E]): + def __init__(self, error): # type: (E) -> None + self._error = error + + def bind(self, + func): # type: (Callable[[S], Either[R, E]]) -> Either[R, E] + return Left(self._error) + + def __eq__(self, other): # type: (object) -> bool + return isinstance(other, Left) and self._error == other._error + + def __str__(self): # type: () -> str + return "Left: %s" % self._error diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/failure_reason.py trash-cli-0.23.11.10/trashcli/put/core/failure_reason.py --- trash-cli-0.23.2.13.2/trashcli/put/core/failure_reason.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/failure_reason.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,27 @@ +from abc import abstractmethod +from typing import NamedTuple + +from trashcli.compat import Protocol +from trashcli.lib.environ import Environ +from trashcli.put.core.candidate import Candidate + + +class LogContext(NamedTuple('LogContext', [ + ('trashee_path', str), + ('candidate', Candidate), + ('environ', Environ), +])): + def shrunk_candidate_path(self): + return self.candidate.shrink_user(self.environ) + + def trash_dir_norm_path(self): + return self.candidate.norm_path() + + def files_dir(self): + return self.candidate.files_dir() + + +class FailureReason(Protocol): + @abstractmethod + def log_entries(self, context): # type: (LogContext) -> str + raise NotImplementedError diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/int_generator.py trash-cli-0.23.11.10/trashcli/put/core/int_generator.py --- trash-cli-0.23.2.13.2/trashcli/put/core/int_generator.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/int_generator.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,9 @@ +from trashcli.compat import Protocol + + +class IntGenerator(Protocol): + def new_int(self, + min, # type: int + max, # type: int + ): # type: (...) -> int + raise NotImplementedError diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/logs.py trash-cli-0.23.11.10/trashcli/put/core/logs.py --- trash-cli-0.23.2.13.2/trashcli/put/core/logs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/logs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,15 @@ +from enum import Enum +from typing import NamedTuple + + +class Level(Enum): + INFO = "INFO" + DEBUG_FUNC = "DEBUG_FUNC" + DEBUG = "DEBUG" + + +class LogEntry(NamedTuple('LogEntry', [ + ('level', Level), + ('message', str), +])): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/path_maker_type.py trash-cli-0.23.11.10/trashcli/put/core/path_maker_type.py --- trash-cli-0.23.2.13.2/trashcli/put/core/path_maker_type.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/path_maker_type.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,6 @@ +from enum import Enum + + +class PathMakerType(Enum): + AbsolutePaths = 'AbsolutePaths' + RelativePaths = 'RelativePaths' diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/trash_all_result.py trash-cli-0.23.11.10/trashcli/put/core/trash_all_result.py --- trash-cli-0.23.2.13.2/trashcli/put/core/trash_all_result.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/trash_all_result.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,8 @@ +from typing import NamedTuple, List + + +class TrashAllResult(NamedTuple('TrashAllResult', [ + ('failed_paths', List[str]), +])): + def any_failure(self): # type: () -> bool + return len(self.failed_paths) > 0 diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/trash_result.py trash-cli-0.23.11.10/trashcli/put/core/trash_result.py --- trash-cli-0.23.2.13.2/trashcli/put/core/trash_result.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/trash_result.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,6 @@ +from enum import Enum + + +class TrashResult(Enum): + Failure = "Failure" + Success = "Success" diff -Nru trash-cli-0.23.2.13.2/trashcli/put/core/trashee.py trash-cli-0.23.11.10/trashcli/put/core/trashee.py --- trash-cli-0.23.2.13.2/trashcli/put/core/trashee.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/core/trashee.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,8 @@ +from typing import NamedTuple + + +class Trashee(NamedTuple('FileToBeTrashed', [ + ('path', str), + ('volume', str) +])): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/put/file_trasher.py trash-cli-0.23.11.10/trashcli/put/file_trasher.py --- trash-cli-0.23.2.13.2/trashcli/put/file_trasher.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/file_trasher.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,33 +1,35 @@ -from typing import Dict, NamedTuple +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy -from trashcli.fstab import Volumes -from trashcli.put.my_logger import MyLogger, LogData -from trashcli.put.fs.parent_realpath import ParentRealpath +from trashcli.fstab.volume_of import VolumeOf +from trashcli.lib.environ import Environ +from trashcli.put.core.trash_result import TrashResult +from trashcli.put.core.trashee import Trashee +from trashcli.put.fs.parent_realpath import ParentRealpathFs +from trashcli.put.fs.volume_of_parent import VolumeOfParent +from trashcli.put.janitor import Janitor +from trashcli.put.my_logger import LogData +from trashcli.put.my_logger import MyLogger from trashcli.put.reporter import TrashPutReporter from trashcli.put.trash_directories_finder import TrashDirectoriesFinder -from trashcli.put.trash_file_in import TrashFileIn -from trashcli.put.trashee import Trashee -from trashcli.put.trash_result import TrashResult -from trashcli.put.fs.volume_of_parent import VolumeOfParent class FileTrasher: def __init__(self, - volumes, # type: Volumes + volumes, # type: VolumeOf trash_directories_finder, # type: TrashDirectoriesFinder - parent_realpath, # type: ParentRealpath + parent_realpath_fs, # type: ParentRealpathFs logger, # type: MyLogger reporter, # type: TrashPutReporter - trash_file_in=None, # type: TrashFileIn - volume_of_parent=None, # type: VolumeOfParent + janitor, # type: Janitor + volume_of_parent, # type: VolumeOfParent ): # type: (...) -> None self.volumes = volumes self.trash_directories_finder = trash_directories_finder - self.parent_realpath = parent_realpath + self.parent_realpath_fs = parent_realpath_fs self.logger = logger self.reporter = reporter - self.trash_file_in = trash_file_in + self.janitor = janitor self.volume_of_parent = volume_of_parent or volume_of_parent def trash_file(self, @@ -35,31 +37,40 @@ forced_volume, user_trash_dir, home_fallback, - result, # type: TrashResult - environ, # type: Dict[str, str] + environ, # type: Environ uid, # type: int log_data, # type: LogData ): - volume_of_file_to_be_trashed = forced_volume or \ - self.volume_of_parent.volume_of_parent( - path) - file_be_trashed = Trashee(path, volume_of_file_to_be_trashed) - candidates = self.trash_directories_finder. \ - possible_trash_directories_for(volume_of_file_to_be_trashed, + volume = self._figure_out_volume(path, forced_volume) + trashee = Trashee(path, volume) + candidates = self._select_candidates(volume, user_trash_dir, environ, + uid, home_fallback) + failures = [] + for candidate in candidates: + self.reporter.trash_dir_with_volume(candidate, log_data) + trashing = self.janitor.trash_file_in( + candidate, log_data, environ, trashee) + if trashing.succeeded(): + self.reporter.file_has_been_trashed_in_as(path, + candidate, + log_data, + environ) + return TrashResult.Success + else: + failures.append((candidate, trashing.reason)) + self.reporter.unable_to_trash_file2(trashee, log_data, failures, + environ) + return TrashResult.Failure + + def _figure_out_volume(self, path, default_volume): + if default_volume: + return default_volume + else: + return self.volume_of_parent.volume_of_parent(path) + + def _select_candidates(self, volume, user_trash_dir, environ, uid, + home_fallback): + return self.trash_directories_finder. \ + possible_trash_directories_for(volume, user_trash_dir, environ, uid, home_fallback) - self.reporter.volume_of_file(volume_of_file_to_be_trashed, log_data) - file_has_been_trashed = False - for candidate in candidates: - file_has_been_trashed = file_has_been_trashed or \ - self.trash_file_in.trash_file_in(candidate, - log_data, - environ, - file_be_trashed) - if file_has_been_trashed: break - - if not file_has_been_trashed: - result = result.mark_unable_to_trash_file() - self.reporter.unable_to_trash_file(path, log_data) - - return result diff -Nru trash-cli-0.23.2.13.2/trashcli/put/format_trash_info.py trash-cli-0.23.11.10/trashcli/put/format_trash_info.py --- trash-cli-0.23.2.13.2/trashcli/put/format_trash_info.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/format_trash_info.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,16 +1,20 @@ -from trashcli.py2compat import url_quote +import datetime +from six.moves.urllib.parse import quote as url_quote -def format_trashinfo(original_location, deletion_date): + +def format_trashinfo(original_location, # type: str + deletion_date, # type: datetime.datetime + ): content = ("[Trash Info]\n" + "Path=%s\n" % format_original_location(original_location) + "DeletionDate=%s\n" % format_date(deletion_date)).encode('utf-8') return content -def format_date(deletion_date): +def format_date(deletion_date): # type: (datetime.datetime) -> str return deletion_date.strftime("%Y-%m-%dT%H:%M:%S") -def format_original_location(original_location): +def format_original_location(original_location): # type: (str) -> str return url_quote(original_location, '/') diff -Nru trash-cli-0.23.2.13.2/trashcli/put/fs/fs.py trash-cli-0.23.11.10/trashcli/put/fs/fs.py --- trash-cli-0.23.2.13.2/trashcli/put/fs/fs.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/fs/fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,71 +1,79 @@ -from abc import ABCMeta, abstractmethod +import os +from abc import abstractmethod +from trashcli.compat import Protocol -import six +class RealPathFs(Protocol): + @abstractmethod + def realpath(self, path): + raise NotImplementedError -@six.add_metaclass(ABCMeta) -class Fs: +class Fs(RealPathFs, Protocol): @abstractmethod def atomic_write(self, path, content): - pass + raise NotImplementedError @abstractmethod def chmod(self, path, mode): - pass + raise NotImplementedError @abstractmethod def isdir(self, path): - pass + raise NotImplementedError @abstractmethod def isfile(self, path): - pass + raise NotImplementedError @abstractmethod def getsize(self, path): - pass + raise NotImplementedError @abstractmethod def exists(self, path): - pass + raise NotImplementedError @abstractmethod def makedirs(self, path, mode): - pass + raise NotImplementedError @abstractmethod def move(self, path, dest): - pass + raise NotImplementedError @abstractmethod def remove_file(self, path): - pass + raise NotImplementedError @abstractmethod def islink(self, path): - pass + raise NotImplementedError @abstractmethod def has_sticky_bit(self, path): - pass - - @abstractmethod - def realpath(self, path): - pass + raise NotImplementedError @abstractmethod def is_accessible(self, path): - pass + raise NotImplementedError @abstractmethod def make_file(self, path, content): - pass + raise NotImplementedError @abstractmethod def get_mod(self, path): - pass + raise NotImplementedError @abstractmethod def lexists(self, path): - pass + raise NotImplementedError + + @abstractmethod + def walk_no_follow(self, top): + raise NotImplementedError + + def parent_realpath2(self, path): + parent = os.path.dirname(path) + return self.realpath(parent) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/fs/parent_realpath.py trash-cli-0.23.11.10/trashcli/put/fs/parent_realpath.py --- trash-cli-0.23.2.13.2/trashcli/put/fs/parent_realpath.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/fs/parent_realpath.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,7 +1,7 @@ import os -class ParentRealpath: +class ParentRealpathFs: def __init__(self, fs): self.fs = fs diff -Nru trash-cli-0.23.2.13.2/trashcli/put/fs/real_fs.py trash-cli-0.23.11.10/trashcli/put/fs/real_fs.py --- trash-cli-0.23.2.13.2/trashcli/put/fs/real_fs.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/fs/real_fs.py 2023-11-10 07:15:04.000000000 +0000 @@ -3,7 +3,6 @@ from trashcli import fs from trashcli.fs import write_file -from trashcli.put.fs.size_counter import SizeCounter from trashcli.put.fs.fs import Fs @@ -26,7 +25,7 @@ def walk_no_follow(self, path): try: - import scandir + import scandir # type: ignore walk = scandir.walk except ImportError: walk = os.walk diff -Nru trash-cli-0.23.2.13.2/trashcli/put/fs/volume_of_parent.py trash-cli-0.23.11.10/trashcli/put/fs/volume_of_parent.py --- trash-cli-0.23.2.13.2/trashcli/put/fs/volume_of_parent.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/fs/volume_of_parent.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,15 +1,15 @@ -from trashcli.fstab import Volumes -from trashcli.put.fs.parent_realpath import ParentRealpath +from trashcli.fstab.volume_of import VolumeOf +from trashcli.put.fs.parent_realpath import ParentRealpathFs class VolumeOfParent: def __init__(self, - volumes, # type: Volumes - parent_realpath, # type: ParentRealpath + volumes, # type: VolumeOf + parent_realpath_fs, # type: ParentRealpathFs ): self.volumes = volumes - self.parent_realpath = parent_realpath + self.parent_realpath_fs = parent_realpath_fs def volume_of_parent(self, path): - parent_realpath = self.parent_realpath.parent_realpath(path) + parent_realpath = self.parent_realpath_fs.parent_realpath(path) return self.volumes.volume_of(parent_realpath) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/gate.py trash-cli-0.23.11.10/trashcli/put/gate.py --- trash-cli-0.23.2.13.2/trashcli/put/gate.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/gate.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,21 +1,9 @@ -import six -from trashcli.put.class_name_meta import ClassNameMeta +from enum import Enum -class Gate(object): - pass +class Gate(Enum): + HomeFallback = "HomeFallbackGate" + SameVolume = "SameVolumeGate" - -@six.add_metaclass(ClassNameMeta) -class ClosedGate(Gate): - pass - - -@six.add_metaclass(ClassNameMeta) -class HomeFallbackGate(Gate): - pass - - -@six.add_metaclass(ClassNameMeta) -class SameVolumeGate(Gate): - pass + def __repr__(self): + return "%s.%s" % (type(self).__name__, self.name) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/gate_impl.py trash-cli-0.23.11.10/trashcli/put/gate_impl.py --- trash-cli-0.23.2.13.2/trashcli/put/gate_impl.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/gate_impl.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,87 +0,0 @@ -from typing import Dict, NamedTuple, Optional - -from trashcli.put.candidate import Candidate -from trashcli.put.fs.fs import Fs -from trashcli.put.trash_dir_volume_reader import TrashDirVolumeReader -from trashcli.put.trashee import Trashee -from trashcli.put.volume_message_formatter import VolumeMessageFormatter - - -class GateCheckResult(NamedTuple('GateCheckResult', [ - ('ok', bool), - ('reason', Optional[str]), -])): - - @staticmethod - def make_ok(): - return GateCheckResult(True, None) - - @staticmethod - def make_error(reason): - return GateCheckResult(False, reason) - - def __repr__(self): - if self.ok and self.reason is None: - return 'GateCheckResult.ok()' - if not self.ok and self.reason is not None: - return 'GateCheckResult.error(%r)' % self.reason - return 'GateCheckResult(%s, %r)' % (self.ok, self.reason) - - -class GateImpl(object): - @staticmethod - def can_trash_in(trashee, # type: Trashee - candidate, # type: Candidate - environ, # type: Dict[str, str] - ): # type (...) -> GateCheckResult - pass - - -class ClosedGateImpl(GateImpl): - - def can_trash_in(self, - trashee, # type: Trashee - candidate, # type: Candidate - environ, # type: Dict[str, str] - ): - return GateCheckResult.make_error("trash dir not enabled: %s" % - candidate.shrink_user(environ)) - - -class HomeFallbackGateImpl(GateImpl): - def __init__(self, - fs, # type: Fs - ): - self.fs = fs - - def can_trash_in(self, - trashee, # type: Trashee - candidate, # type: Candidate - environ, # type: Dict[str, str] - ): - if environ.get('TRASH_ENABLE_HOME_FALLBACK', None) == "1": - return GateCheckResult.make_ok() - return GateCheckResult.make_error("trash dir not enabled: %s" % - candidate.shrink_user(environ)) - - -class SameVolumeGateImpl(GateImpl): - def __init__(self, - trash_dir_volume, # type: TrashDirVolumeReader - ): - self.trash_dir_volume = trash_dir_volume - - def can_trash_in(self, - trashee, # type: Trashee - candidate, # type: Candidate - environ, # type: Dict[str, str] - ): - same_volume = self.trash_dir_volume.volume_of_trash_dir( - candidate.trash_dir_path) == trashee.volume - - if not same_volume: - msg_formatter = VolumeMessageFormatter() - message = msg_formatter.format_msg(trashee, candidate, environ) - return GateCheckResult.make_error(message) - - return GateCheckResult.make_ok() diff -Nru trash-cli-0.23.2.13.2/trashcli/put/info_dir.py trash-cli-0.23.11.10/trashcli/put/info_dir.py --- trash-cli-0.23.2.13.2/trashcli/put/info_dir.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/info_dir.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -import errno -import os - -from trashcli.put.fs.fs import Fs -from trashcli.put.my_logger import MyLogger, LogData -from trashcli.put.suffix import Suffix - - -class InfoDir: - def __init__(self, - fs, # type: Fs - logger, # type: MyLogger - suffix, # type: Suffix - ): # type: (...) -> None - self.fs = fs - self.logger = logger - self.suffix = suffix - - def persist_trash_info(self, - basename, #type: str - content, #type: str - log_data, #type: LogData - info_dir_path, #type: str - ): - """ - Create a .trashinfo file in the $trash/info directory. - returns the created TrashInfoFile. - """ - - index = 0 - name_too_long = False - while True: - suffix = self.suffix.suffix_for_index(index) - trashinfo_basename = create_trashinfo_basename(basename, - suffix, - name_too_long) - trashinfo_path = os.path.join(info_dir_path, trashinfo_basename) - try: - self.fs.atomic_write(trashinfo_path, content) - self.logger.debug(".trashinfo created as %s." % trashinfo_path, - log_data) - return trashinfo_path - except OSError as e: - if e.errno == errno.ENAMETOOLONG: - name_too_long = True - self.logger.debug( - "attempt for creating %s failed." % trashinfo_path, - log_data) - - index += 1 - - -def create_trashinfo_basename(basename, suffix, name_too_long): - after_basename = suffix + ".trashinfo" - if name_too_long: - truncated_basename = basename[0:len(basename) - len(after_basename)] - else: - truncated_basename = basename - return truncated_basename + after_basename diff -Nru trash-cli-0.23.2.13.2/trashcli/put/janitor.py trash-cli-0.23.11.10/trashcli/put/janitor.py --- trash-cli-0.23.2.13.2/trashcli/put/janitor.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/janitor.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,91 @@ +from typing import NamedTuple, TypeVar + +from trashcli.lib.environ import Environ +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.either import Left +from trashcli.put.core.failure_reason import FailureReason, LogContext +from trashcli.put.core.trashee import Trashee +from trashcli.put.fs.fs import Fs +from trashcli.put.janitor_tools.info_creator import TrashInfoCreator +from trashcli.put.janitor_tools.info_file_persister import InfoFilePersister, \ + TrashedFile +from trashcli.put.janitor_tools.put_trash_dir import PutTrashDir +from trashcli.put.janitor_tools.security_check import SecurityCheck +from trashcli.put.janitor_tools.trash_dir_checker import TrashDirChecker +from trashcli.put.janitor_tools.trash_dir_creator import TrashDirCreator +from trashcli.put.jobs import JobExecutor +from trashcli.put.my_logger import LogData +from trashcli.put.my_logger import MyLogger + + +class NoLog(FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return "" + + +class Janitor: + def __init__(self, + fs, # type: Fs + trash_dir, # type: PutTrashDir + trashing_checker, # type: TrashDirChecker + info_dir, # type: TrashInfoCreator + persister, # type: InfoFilePersister + logger, # type: MyLogger + ): + self.trash_dir = trash_dir + self.trashing_checker = trashing_checker + self.info_dir = info_dir + self.security_check = SecurityCheck(fs) + self.persister = persister + self.dir_creator = TrashDirCreator(fs) + self.executor = JobExecutor(logger, TrashedFile) + + class Result(NamedTuple('Result', [ + ('ok', bool), + ('reason', FailureReason) + ])): + def succeeded(self): + return self.ok + + def trash_file_in(self, + candidate, # type: Candidate + log_data, # type: LogData + environ, # type: Environ + trashee, # type: Trashee + ): # type: (...) -> Result + secure = self.security_check.check_trash_dir_is_secure(candidate) + if isinstance(secure, Left): + return make_error(secure) + + can_be_used = self.trashing_checker.file_could_be_trashed_in( + trashee, candidate, environ) + if isinstance(can_be_used, Left): + return make_error(can_be_used) + + dirs_creation = self.dir_creator.make_candidate_dirs(candidate) + if isinstance(dirs_creation, Left): + return make_error(dirs_creation) + + trashinfo_data = self.info_dir.make_trashinfo_data( + trashee.path, candidate) + if isinstance(trashinfo_data, Left): + return make_error(trashinfo_data) + + persisting_job = self.persister.try_persist(trashinfo_data.value()) + trashed_file = self.executor.execute(persisting_job, log_data) + trashed = self.trash_dir.try_trash(trashee.path, trashed_file) + if isinstance(trashed, Left): + return make_error(trashed) + + return make_ok() + + +S = TypeVar('S') + + +def make_error(reason): # type: (Left[S, FailureReason]) -> Janitor.Result + return Janitor.Result(False, reason.error()) + + +def make_ok(): # type: () -> Janitor.Result + return Janitor.Result(True, NoLog()) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/info_creator.py trash-cli-0.23.11.10/trashcli/put/janitor_tools/info_creator.py --- trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/info_creator.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/janitor_tools/info_creator.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,49 @@ +import os +from typing import NamedTuple + +from trashcli.put.clock import PutClock +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.either import Either, Right, Left +from trashcli.put.core.failure_reason import FailureReason +from trashcli.put.core.failure_reason import LogContext +from trashcli.put.format_trash_info import format_trashinfo +from trashcli.put.janitor_tools.info_file_persister import InfoFilePersister +from trashcli.put.janitor_tools.info_file_persister import TrashinfoData +from trashcli.put.original_location import OriginalLocation + + +class UnableToCreateTrashInfoContent( + NamedTuple('UnableToCreateTrashInfoContent', [ + ('error', Exception), + ]), FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return "failed to generate trashinfo content: %s" % ( + self.error) + + +class TrashInfoCreator: + def __init__(self, + persister, # type: InfoFilePersister + original_location, # type: OriginalLocation + clock, # type: PutClock + ): + self.original_location = original_location + self.clock = clock + + Result = Either[TrashinfoData, UnableToCreateTrashInfoContent] + + def make_trashinfo_data(self, + path, # type: str + candidate, # type: Candidate + ): # type: (...) -> Result + try: + original_location = self.original_location.for_file(path, + candidate.path_maker_type, + candidate.volume) + content = format_trashinfo(original_location, self.clock.now()) + basename = os.path.basename(original_location) + trash_info_data = TrashinfoData(basename, content, + candidate.info_dir()) + return Right(trash_info_data) + except (IOError, OSError) as error: + return Left(UnableToCreateTrashInfoContent(error)) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/info_file_persister.py trash-cli-0.23.11.10/trashcli/put/janitor_tools/info_file_persister.py --- trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/info_file_persister.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/janitor_tools/info_file_persister.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,82 @@ +import errno +import os +from typing import NamedTuple, Iterator + +from trashcli.lib.path_of_backup_copy import path_of_backup_copy +from trashcli.put.fs.fs import Fs +from trashcli.put.jobs import JobStatus, NeedsMoreAttempts, Succeeded, \ + JobExecutor +from trashcli.put.my_logger import LogData, MyLogger +from trashcli.put.suffix import Suffix + + +class TrashinfoData(NamedTuple('TrashinfoData', [ + ('basename', str), + ('content', str), + ('info_dir_path', str), +])): + pass + + +class TrashedFile(NamedTuple('TrashedFile', [ + ('trashinfo_path', str), +])): + @property + def backup_copy_path(self): # type: () -> str + return path_of_backup_copy(self.trashinfo_path) + + +class InfoFilePersister: + def __init__(self, + fs, # type: Fs + logger, # type: MyLogger + suffix, # type: Suffix + ): # type: (...) -> None + self.fs = fs + self.logger = logger + self.suffix = suffix + + def create_trashinfo_file(self, + trashinfo_data, # type: TrashinfoData + log_data, # type: LogData + ): # type: (...) -> TrashedFile + return JobExecutor(self.logger, TrashedFile).execute( + self.try_persist(trashinfo_data), log_data) + + Result = Iterator[JobStatus[TrashedFile]] + + def try_persist(self, + data, # type: TrashinfoData + ): # type: (...) -> Result + index = 0 + name_too_long = False + while True: + suffix = self.suffix.suffix_for_index(index) + trashinfo_basename = create_trashinfo_basename(data.basename, + suffix, + name_too_long) + trashinfo_path = os.path.join(data.info_dir_path, + trashinfo_basename) + if os.path.exists(path_of_backup_copy(trashinfo_path)): + index += 1 + continue + try: + self.fs.atomic_write(trashinfo_path, data.content) + yield Succeeded(TrashedFile(trashinfo_path), + ".trashinfo created as %s." % trashinfo_path) + except OSError as e: + if e.errno == errno.ENAMETOOLONG: + name_too_long = True + yield NeedsMoreAttempts(trashinfo_path, + "attempt for creating %s failed." % trashinfo_path) + + index += 1 + + +def create_trashinfo_basename(basename, suffix, name_too_long): + after_basename = suffix + ".trashinfo" + if name_too_long: + truncated_basename = basename[0:len(basename) - len(after_basename)] + else: + truncated_basename = basename + return truncated_basename + after_basename diff -Nru trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/put_trash_dir.py trash-cli-0.23.11.10/trashcli/put/janitor_tools/put_trash_dir.py --- trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/put_trash_dir.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/janitor_tools/put_trash_dir.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,39 @@ +from typing import NamedTuple + +from trashcli.put.core.either import Either, Right, Left +from trashcli.put.core.failure_reason import FailureReason +from trashcli.put.core.failure_reason import LogContext +from trashcli.put.fs.fs import Fs +from trashcli.put.janitor_tools.info_creator import \ + TrashInfoCreator +from trashcli.put.janitor_tools.info_file_persister import TrashedFile + + +class UnableToMoveFileToTrash(NamedTuple('UnableToMoveFileToTrash', [ + ('error', Exception), +]), FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return "failed to move %s in %s: %s" % ( + context.trashee_path, + context.files_dir(), + self.error) + + +class PutTrashDir: + def __init__(self, + fs, # type: Fs + info_dir2, # type: TrashInfoCreator + ): + self.fs = fs + self.info_dir2 = info_dir2 + + def try_trash(self, + path, # type: str + paths, # type: TrashedFile + ): # type: (...) -> Either[None, Exception] + try: + self.fs.move(path, paths.backup_copy_path) + return Right(None) + except (IOError, OSError) as error: + self.fs.remove_file(paths.trashinfo_path) + return Left(UnableToMoveFileToTrash(error)) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/security_check.py trash-cli-0.23.11.10/trashcli/put/janitor_tools/security_check.py --- trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/security_check.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/janitor_tools/security_check.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,61 @@ +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.check_type import NoCheck, TopTrashDirCheck +from trashcli.put.core.either import Either, Right, Left +from trashcli.put.core.failure_reason import FailureReason, LogContext + + +class SecurityCheck: + + def __init__(self, fs): + self.fs = fs + + def check_trash_dir_is_secure(self, + candidate, # type: Candidate + ): # type: (...) -> Either[None, FailureReason] + if candidate.check_type == NoCheck: + return Right(None) + if candidate.check_type == TopTrashDirCheck: + parent = candidate.parent_dir() + if not self.fs.lexists(parent): + return Left(TrashDirDoesNotHaveParent()) + if not self.fs.isdir(parent): + return Left(TrashDirCannotBeCreatedBecauseParentIsFile()) + if self.fs.islink(parent): + return Left(TrashDirIsNotSecureBecauseSymLink()) + if not self.fs.has_sticky_bit(parent): + return Left(TrashDirIsNotSecureBecauseNotSticky()) + return Right(None) + raise Exception("Unknown check type: %s" % candidate.check_type) + + +class TrashDirDoesNotHaveParent(FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return trash_dir_parent_problem(context, ( + "trash dir cannot be created because its parent does not exists")) + + +class TrashDirCannotBeCreatedBecauseParentIsFile(FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return trash_dir_parent_problem(context, ( + "trash dir cannot be created as its parent is a file instead of being a directory")) + + +class TrashDirIsNotSecureBecauseSymLink(FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return trash_dir_parent_problem(context, ( + "trash dir is insecure, its parent should not be a symlink")) + + +class TrashDirIsNotSecureBecauseNotSticky(FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return trash_dir_parent_problem(context, ( + "trash dir is insecure, its parent should be sticky")) + + +def trash_dir_parent_problem(context, # type: LogContext + message, # type: str + ): # type: (...) -> str + return "%s, trash-dir: %s, parent: %s" % ( + message, + context.trash_dir_norm_path(), + context.candidate.parent_dir()) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/trash_dir_checker.py trash-cli-0.23.11.10/trashcli/put/janitor_tools/trash_dir_checker.py --- trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/trash_dir_checker.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/janitor_tools/trash_dir_checker.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,87 @@ +from typing import NamedTuple + +from trashcli.fstab.volume_of import VolumeOf +from trashcli.lib.environ import Environ +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.either import Either, Right, Left +from trashcli.put.core.failure_reason import FailureReason, LogContext +from trashcli.put.core.trashee import Trashee +from trashcli.put.fs.fs import Fs +from trashcli.put.gate import Gate +from trashcli.put.trash_dir_volume_reader import TrashDirVolumeReader + + +class DifferentVolumes(NamedTuple('DifferentVolumes', [ + ('trash_dir_volume', str), + ('file_volume', str), +]), FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return ( + "trash dir and file to be trashed are not in the same volume, trash-dir volume: %s, file volume: %s" + % (self.trash_dir_volume, self.file_volume)) + + +class HomeFallBackNotEnabled(FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return "home fallback not enabled" + + def __eq__(self, other): + return isinstance(other, HomeFallBackNotEnabled) + + +GateCheckResult = Either[None, FailureReason] + + +class TrashDirChecker: + def __init__(self, fs, volumes): # type: (Fs, VolumeOf) -> None + self.fs = fs + self.volumes = volumes + + def file_could_be_trashed_in(self, + trashee, # type: Trashee + candidate, # type: Candidate + environ, # type: Environ + ): # type: (...) -> GateCheckResult + if candidate.gate is Gate.HomeFallback: + return self._can_be_trashed_in_home_trash_dir(environ) + elif candidate.gate is Gate.SameVolume: + return SameVolumeGateImpl(self.volumes, self.fs).can_trash_in( + trashee, candidate) + else: + raise ValueError("Unknown gate: %s" % candidate.gate) + + @staticmethod + def _can_be_trashed_in_home_trash_dir(environ, # type: Environ + ): + if environ.get('TRASH_ENABLE_HOME_FALLBACK', None) == "1": + return make_ok() + return Left(HomeFallBackNotEnabled()) + + +def make_ok(): + return Right(None) + + +class SameVolumeGateImpl: + def __init__(self, + volumes, # type: VolumeOf + fs, # type: Fs + ): + self.volumes = volumes + self.fs = fs + + def can_trash_in(self, + trashee, # type: Trashee + candidate, # type: Candidate + ): + trash_dir_volume = self._volume_of_trash_dir(candidate) + same_volume = trash_dir_volume == trashee.volume + + if not same_volume: + return Left(DifferentVolumes(trash_dir_volume, trashee.volume)) + + return make_ok() + + def _volume_of_trash_dir(self, candidate): # type: (Candidate) -> str + return (TrashDirVolumeReader(self.volumes, self.fs) + .volume_of_trash_dir(candidate.trash_dir_path)) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/trash_dir_creator.py trash-cli-0.23.11.10/trashcli/put/janitor_tools/trash_dir_creator.py --- trash-cli-0.23.2.13.2/trashcli/put/janitor_tools/trash_dir_creator.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/janitor_tools/trash_dir_creator.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,31 @@ +from typing import NamedTuple + +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.either import Either, Right, Left +from trashcli.put.core.failure_reason import FailureReason, LogContext +from trashcli.put.dir_maker import DirMaker +from trashcli.put.fs.fs import Fs + + +class TrashDirCannotBeCreated( + NamedTuple('TrashDirCannotBeCreated', [ + ('error', Exception), + ]), FailureReason): + def log_entries(self, context): # type: (LogContext) -> str + return "error during directory creation: %s" % ( + self.error) + + +class TrashDirCreator: + def __init__(self, fs): # type: (Fs) -> None + self.dir_maker = DirMaker(fs) + + def make_candidate_dirs(self, + candidate): # type: (Candidate) -> Either[None, TrashDirCannotBeCreated] + try: + self.dir_maker.mkdir_p(candidate.trash_dir_path, 0o700) + self.dir_maker.mkdir_p(candidate.files_dir(), 0o700) + self.dir_maker.mkdir_p(candidate.info_dir(), 0o700) + return Right(None) + except (IOError, OSError) as error: + return Left(TrashDirCannotBeCreated(error)) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/jobs.py trash-cli-0.23.11.10/trashcli/put/jobs.py --- trash-cli-0.23.2.13.2/trashcli/put/jobs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/jobs.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,65 @@ +from typing import Generic, Iterator, TypeVar, Type + +from trashcli.put.core.logs import LogEntry, Level +from trashcli.put.my_logger import MyLogger, LogData + +R = TypeVar('R') + + +class JobStatus(Generic[R]): + def __init__(self, message): # type: (str) -> None + self.log_entries = [LogEntry(Level.DEBUG, message)] + + def has_succeeded(self): + return isinstance(self, Succeeded) + + def result(self): # type: () -> R + raise NotImplementedError + + def logs(self): + return self.log_entries + + +class NeedsMoreAttempts(JobStatus, Generic[R]): + def __init__(self, + trashinfo_path, # type: str + message, # type: str + ): + super(NeedsMoreAttempts, self).__init__(message) + self.trashinfo_path = trashinfo_path + + def result(self): # type: () -> R + raise ValueError("Result not available yet!") + + +class Succeeded(JobStatus, Generic[R]): + def __init__(self, + result, # type: R + message, # type: str + ): + super(Succeeded, self).__init__(message) + self._result = result # type: R + + def result(self): # type: () -> R + return self._result + + def __repr__(self): + return "Succeeded(%s)" % repr(self.result) + + +class JobExecutor(Generic[R]): + def __init__(self, + logger, # type: MyLogger + _result_type, # type: Type[R] + ): + self.logger = logger + + def execute(self, + job, # type: Iterator[JobStatus[R]] + log_data, # type: LogData + ): # type: (...) -> R + for status in job: + self.logger.log_multiple(status.logs(), log_data) + if status.has_succeeded(): + return status.result() + raise ValueError("Should not happen!") diff -Nru trash-cli-0.23.2.13.2/trashcli/put/main.py trash-cli-0.23.11.10/trashcli/put/main.py --- trash-cli-0.23.2.13.2/trashcli/put/main.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/main.py 2023-11-10 07:15:04.000000000 +0000 @@ -2,77 +2,88 @@ import random import sys -import trashcli.fstab -import trashcli.trash -from trashcli.lib.my_input import my_input +from trashcli.fstab.volume_of import RealVolumeOf +from trashcli.lib.environ import cast_environ +from trashcli.lib.my_input import Input +from trashcli.lib.my_input import RealInput from trashcli.put.clock import RealClock +from trashcli.put.core.int_generator import IntGenerator from trashcli.put.describer import Describer -from trashcli.put.dir_maker import DirMaker from trashcli.put.file_trasher import FileTrasher -from trashcli.put.gate import ClosedGate, HomeFallbackGate, SameVolumeGate -from trashcli.put.gate_impl import ClosedGateImpl, HomeFallbackGateImpl, \ - SameVolumeGateImpl -from trashcli.put.info_dir import InfoDir +from trashcli.put.fs.fs import Fs +from trashcli.put.fs.parent_realpath import ParentRealpathFs +from trashcli.put.fs.real_fs import RealFs +from trashcli.put.fs.volume_of_parent import VolumeOfParent +from trashcli.put.janitor import Janitor +from trashcli.put.janitor_tools.info_creator import \ + TrashInfoCreator +from trashcli.put.janitor_tools.info_file_persister import InfoFilePersister +from trashcli.put.janitor_tools.put_trash_dir import PutTrashDir +from trashcli.put.janitor_tools.trash_dir_checker import TrashDirChecker from trashcli.put.my_logger import MyLogger from trashcli.put.original_location import OriginalLocation -from trashcli.put.fs.parent_realpath import ParentRealpath -from trashcli.put.path_maker import PathMaker -from trashcli.put.fs.real_fs import RealFs from trashcli.put.reporter import TrashPutReporter from trashcli.put.suffix import Suffix from trashcli.put.trash_all import TrashAll -from trashcli.put.trash_dir_volume_reader import TrashDirVolumeReader from trashcli.put.trash_directories_finder import TrashDirectoriesFinder -from trashcli.put.trash_directory_for_put import TrashDirectoryForPut -from trashcli.put.trash_file_in import TrashFileIn -from trashcli.put.trashing_checker import TrashingChecker from trashcli.put.trash_put_cmd import TrashPutCmd from trashcli.put.trasher import Trasher from trashcli.put.user import User -from trashcli.put.fs.volume_of_parent import VolumeOfParent def main(): - cmd = make_cmd(clock=RealClock(), fs=RealFs(), - my_input=my_input, randint=random.randint, - stderr=sys.stderr, volumes=trashcli.fstab.volumes) - return cmd.run(sys.argv, os.environ, os.getuid()) + cmd = make_cmd(clock=RealClock(), + fs=RealFs(), + user_input=RealInput(), + randint=RandomIntGenerator(), + stderr=sys.stderr, + volumes=RealVolumeOf()) + try: + uid = int(os.environ["TRASH_PUT_FAKE_UID_FOR_TESTING"]) + except KeyError: + uid = os.getuid() + return cmd.run_put(sys.argv, cast_environ(os.environ), uid) -def make_cmd(clock, fs, my_input, randint, stderr, volumes): +def make_cmd(clock, + fs, # type: Fs + user_input, # type: Input + randint, # type: IntGenerator + stderr, + volumes, + ): # type: (...) -> TrashPutCmd logger = MyLogger(stderr) describer = Describer(fs) reporter = TrashPutReporter(logger, describer) suffix = Suffix(randint) - dir_maker = DirMaker(fs) - info_dir = InfoDir(fs, logger, suffix) - path_maker = PathMaker() - parent_realpath = ParentRealpath(fs) - original_location = OriginalLocation(parent_realpath, path_maker) - trash_dir = TrashDirectoryForPut(fs, - info_dir, - original_location, - clock) - trash_dir_volume = TrashDirVolumeReader(volumes, fs) - trashing_checker = TrashingChecker({ - ClosedGate: ClosedGateImpl(), - HomeFallbackGate: HomeFallbackGateImpl(fs), - SameVolumeGate: SameVolumeGateImpl(trash_dir_volume), - }) - trash_file_in = TrashFileIn(fs, - reporter, - trash_dir, - trashing_checker, - dir_maker) - volume_of_parent = VolumeOfParent(volumes, parent_realpath) + persister = InfoFilePersister(fs, logger, suffix) + original_location = OriginalLocation(fs) + info_dir2 = TrashInfoCreator(persister, original_location, clock) + trash_dir = PutTrashDir(fs, info_dir2) + trashing_checker = TrashDirChecker(fs, volumes) + janitor = Janitor(fs, + trash_dir, + trashing_checker, + info_dir2, + persister, + logger) + volume_of_parent = VolumeOfParent(volumes, ParentRealpathFs(fs)) file_trasher = FileTrasher(volumes, TrashDirectoriesFinder(volumes), - parent_realpath, + ParentRealpathFs(fs), logger, reporter, - trash_file_in, + janitor, volume_of_parent) - user = User(my_input, describer) + user = User(user_input, describer) trasher = Trasher(file_trasher, user, reporter, fs) trash_all = TrashAll(logger, trasher) return TrashPutCmd(trash_all, reporter) + + +class RandomIntGenerator(IntGenerator): + def new_int(self, + min, # type: int + max, # type: int + ): + return random.randint(min, max) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/my_logger.py trash-cli-0.23.11.10/trashcli/put/my_logger.py --- trash-cli-0.23.2.13.2/trashcli/put/my_logger.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/my_logger.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,7 +1,8 @@ -from io import StringIO - from typing import IO, Callable, List +from trashcli.put.core.logs import Level +from trashcli.put.core.logs import LogEntry + class LogData: def __init__(self, program_name, verbose): @@ -39,3 +40,12 @@ def warning2(self, message, program_name): self.stderr.write("%s: %s\n" % (program_name, message)) + + def log_multiple(self, entries, log_data): # type: (List[LogEntry], LogData) -> None + for entry in entries: + if entry.level == Level.INFO: + self.info(entry.message, log_data) + elif entry.level == Level.DEBUG: + self.debug(entry.message, log_data) + else: + raise ValueError("unknown level: %s" % entry.level) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/original_location.py trash-cli-0.23.11.10/trashcli/put/original_location.py --- trash-cli-0.23.2.13.2/trashcli/put/original_location.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/original_location.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,26 +1,37 @@ import os -from trashcli.put.fs.parent_realpath import ParentRealpath -from trashcli.put.path_maker import PathMaker, PathMakerType +from trashcli.put.core.path_maker_type import PathMakerType +from trashcli.put.fs.fs import Fs class OriginalLocation: def __init__(self, - parent_realpath, # type: ParentRealpath - path_maker, # type: PathMaker + fs, # type: Fs ): - self.parent_realpath = parent_realpath - self.path_maker = path_maker + self.fs = fs def for_file(self, path, - path_maker_type, # type: PathMakerType + path_maker_type, # type: PathMakerType volume_top_dir, - ): + ): # type: (...) -> str normalized_path = os.path.normpath(path) basename = os.path.basename(normalized_path) - parent = self.parent_realpath.parent_realpath(normalized_path) - parent = self.path_maker.calc_parent_path(parent, volume_top_dir, - path_maker_type) + parent = self.fs.parent_realpath2(normalized_path) + parent = self._calc_parent_path(parent, volume_top_dir, + path_maker_type) return os.path.join(parent, basename) + + @staticmethod + def _calc_parent_path(parent, # type: str + volume_top_dir, # type: str + path_maker_type, # type: PathMakerType + ): + if path_maker_type == PathMakerType.AbsolutePaths: + return parent + if path_maker_type == PathMakerType.RelativePaths: + if (parent == volume_top_dir) or parent.startswith( + volume_top_dir + os.path.sep): + parent = parent[len(volume_top_dir + os.path.sep):] + return parent diff -Nru trash-cli-0.23.2.13.2/trashcli/put/parser.py trash-cli-0.23.11.10/trashcli/put/parser.py --- trash-cli-0.23.2.13.2/trashcli/put/parser.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/parser.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,4 +1,5 @@ import os +import pprint from argparse import SUPPRESS, ArgumentParser, RawDescriptionHelpFormatter from typing import NamedTuple, Any, Union, List, Optional @@ -36,7 +37,7 @@ if len(options.files) <= 0: arg_parser.error("Please specify the files to trash.") except SystemExit as e: - return ExitWithCode(type=ExitWithCode, exit_code=e.code) + return ExitWithCode(type=ExitWithCode, exit_code=ensure_int(e.code)) return Trash(type=Trash, program_name=program_name, @@ -49,6 +50,12 @@ home_fallback=options.home_fallback) +def ensure_int(code): + if not isinstance(code, int): + raise ValueError("code must be an int, got %s" % pprint.pformat(code)) + return code + + def make_parser(program_name): parser = ArgumentParser(prog=program_name, usage="%(prog)s [OPTION]... FILE...", @@ -105,6 +112,6 @@ action="version", version=version) parser.add_argument('files', - nargs='...' + nargs='*' ).complete = TRASH_FILES return parser diff -Nru trash-cli-0.23.2.13.2/trashcli/put/path_maker.py trash-cli-0.23.11.10/trashcli/put/path_maker.py --- trash-cli-0.23.2.13.2/trashcli/put/path_maker.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/path_maker.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -import os - - -class PathMakerType: - pass - - -class AbsolutePaths(PathMakerType): - pass - - -class RelativePaths(PathMakerType): - pass - - -class PathMaker: - def calc_parent_path(self, parent, volume_top_dir, - path_maker_type, # type: PathMakerType - ): - if path_maker_type == AbsolutePaths: - return parent - if path_maker_type == RelativePaths: - if (parent == volume_top_dir) or parent.startswith( - volume_top_dir + os.path.sep): - parent = parent[len(volume_top_dir + os.path.sep):] - return parent diff -Nru trash-cli-0.23.2.13.2/trashcli/put/reporter.py trash-cli-0.23.11.10/trashcli/put/reporter.py --- trash-cli-0.23.2.13.2/trashcli/put/reporter.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/reporter.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,14 +1,23 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy import os import re from pwd import getpwuid +from typing import List +from typing import Tuple from grp import getgrgid -from typing import List -from trashcli.put.candidate import Candidate +from trashcli.lib.environ import Environ +from trashcli.lib.exit_codes import EX_IOERR +from trashcli.lib.exit_codes import EX_OK +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.failure_reason import FailureReason +from trashcli.put.core.failure_reason import LogContext +from trashcli.put.core.trash_all_result import TrashAllResult +from trashcli.put.core.trashee import Trashee from trashcli.put.describer import Describer -from trashcli.put.my_logger import MyLogger, LogData -from trashcli.trash import EX_IOERR, EX_OK +from trashcli.put.my_logger import LogData +from trashcli.put.my_logger import MyLogger class TrashPutReporter: @@ -19,17 +28,40 @@ self.logger = logger self.describer = describer - def describe(self, path): + def _describe(self, path): return self.describer.describe(path) def unable_to_trash_dot_entries(self, file, program_name): self.logger.warning2( - "cannot trash %s '%s'" % (self.describe(file), file), + "cannot trash %s '%s'" % (self._describe(file), file), program_name) - def unable_to_trash_file(self, f, log_data): - self.logger.warning2("cannot trash %s '%s'" % (self.describe(f), f), - log_data.program_name) + def unable_to_trash_file_non_existent(self, + path, # type: str + log_data, # type: LogData + ): + self.logger.warning2("cannot trash %s '%s'" % ( + self._describe(path), path), log_data.program_name) + + # TODO + def unable_to_trash_file2(self, + trashee, # type: Trashee + log_data, # type: LogData + failures, + # type: List[Tuple[Candidate, FailureReason]] + environ, # type: Environ + ): + path = trashee.path + volume = trashee.volume + self.logger.warning2("cannot trash %s '%s' (from volume '%s')" % ( + self._describe(path), path, volume), log_data.program_name) + for candidate, reason in failures: + context = LogContext(path, candidate, environ) + message = " `- failed to trash %s in %s, because %s" % ( + path, + candidate.norm_path(), + reason.log_entries(context)) + self.logger.warning2(message, log_data.program_name) def file_has_been_trashed_in_as(self, trashed_file, @@ -40,13 +72,6 @@ self.logger.info("'%s' trashed in %s" % (trashed_file, trash_dir_path), log_data) - def trash_dir_is_not_secure(self, - path, - log_data, # type: LogData - ): - self.logger.info("trash directory is not secure: %s" % path, - log_data) - def log_info_messages(self, messages, # type: List[str] log_data, # type: LogData @@ -54,24 +79,6 @@ for message in messages: self.logger.info(message, log_data) - def log_info(self, - message, - log_data, # type: LogData - ): - self.logger.info(message, log_data) - - def unable_to_trash_file_in_because(self, - file_to_be_trashed, - trash_dir, # type: Candidate - error, - log_data, # type: LogData - environ): - self.logger.info("failed to trash %s in %s, because: %s" % ( - file_to_be_trashed, trash_dir.shrink_user(environ), error), - log_data) - self.logger.debug_func_result( - lambda: self.log_data_for_debugging(error), log_data) - @classmethod def log_data_for_debugging(cls, error): try: @@ -89,19 +96,28 @@ log_data, # type: LogData ): # type: (...) -> None - self.logger.info( + self.logger.debug( "trying trash dir: %s from volume: %s" % (candidate.norm_path(), candidate.volume), log_data) - def exit_code(self, result): - if not result.some_file_has_not_be_trashed: + def exit_code(self, + result, # type: TrashAllResult + ): # type: (...) -> int + if not result.any_failure(): return EX_OK else: return EX_IOERR - def volume_of_file(self, volume, log_data): - self.logger.info("volume of file: %s" % volume, log_data) + def report_reason(self, + reason, # type: FailureReason + log_data, # type: LogData + environ, # type: Environ + trashee_path, # type: str + candidate, # type: Candidate + ): # type: (...) -> None + context = LogContext(trashee_path, candidate, environ) + self.logger.info(reason.log_entries(context), log_data) def gentle_stat_read(path): @@ -115,7 +131,7 @@ return str(e) -def remove_octal_prefix(s): +def remove_octal_prefix(s): # type: (str) -> str remove_new_octal_format = s.replace('0o', '') remove_old_octal_format = re.sub(r"^0", '', remove_new_octal_format) return remove_old_octal_format diff -Nru trash-cli-0.23.2.13.2/trashcli/put/security_check.py trash-cli-0.23.11.10/trashcli/put/security_check.py --- trash-cli-0.23.2.13.2/trashcli/put/security_check.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/security_check.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -import os - -import six - -from trashcli.put.class_name_meta import ClassNameMeta - - -@six.add_metaclass(ClassNameMeta) -class CheckType: - pass - - -class NoCheck(CheckType): - pass - - -class TopTrashDirCheck(CheckType): - pass - - -class SecurityCheck: - - def __init__(self, fs): - self.fs = fs - - def check_trash_dir_is_secure(self, candidate): - if candidate.check_type == NoCheck: - return True, [] - if candidate.check_type == TopTrashDirCheck: - parent = os.path.dirname(candidate.trash_dir_path) - if not self.fs.isdir(parent): - return False, [ - "found unusable .Trash dir (should be a dir): %s" % parent] - if self.fs.islink(parent): - return False, [ - "found unsecure .Trash dir (should not be a symlink): %s" % parent] - if not self.fs.has_sticky_bit(parent): - return False, [ - "found unsecure .Trash dir (should be sticky): %s" % parent] - return True, [] - raise Exception("Unknown check type: %s" % candidate.check_type) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/suffix.py trash-cli-0.23.11.10/trashcli/put/suffix.py --- trash-cli-0.23.2.13.2/trashcli/put/suffix.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/suffix.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,11 @@ +from trashcli.put.core.int_generator import IntGenerator + + class Suffix: - def __init__(self, randint): - self.randint = randint + def __init__(self, + int_gen, # type: IntGenerator + ): + self.int_gen = int_gen def suffix_for_index(self, index): if index == 0: @@ -8,4 +13,4 @@ elif index < 100: return "_%s" % index else: - return "_%s" % self.randint(0, 65535) + return "_%s" % self.int_gen.new_int(0, 65535) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trash_all.py trash-cli-0.23.11.10/trashcli/put/trash_all.py --- trash-cli-0.23.2.13.2/trashcli/put/trash_all.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trash_all.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,6 @@ +from trashcli.put.core.trash_all_result import TrashAllResult from trashcli.put.my_logger import MyLogger, LogData -from trashcli.put.reporter import TrashPutReporter -from trashcli.put.trash_result import TrashResult +from trashcli.put.core.trash_result import TrashResult from trashcli.put.trasher import Trasher @@ -13,7 +13,7 @@ self.trasher = trasher def trash_all(self, - args, + paths, user_trash_dir, mode, forced_volume, @@ -21,17 +21,20 @@ program_name, log_data, # type: LogData environ, - uid): - result = TrashResult(False) - for arg in args: - result = self.trasher.trash(arg, - user_trash_dir, - result, - mode, - forced_volume, - home_fallback, - program_name, - log_data, - environ, - uid) - return result + uid, + ): # (...) -> Result + failed_paths = [] + for path in paths: + result = self.trasher.trash_single(path, + user_trash_dir, + mode, + forced_volume, + home_fallback, + program_name, + log_data, + environ, + uid) + if result == TrashResult.Failure: + failed_paths.append(path) + + return TrashAllResult(failed_paths) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trash_dir_volume_reader.py trash-cli-0.23.11.10/trashcli/put/trash_dir_volume_reader.py --- trash-cli-0.23.2.13.2/trashcli/put/trash_dir_volume_reader.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trash_dir_volume_reader.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,8 +1,14 @@ import os +from trashcli.fstab.volume_of import VolumeOf +from trashcli.put.fs.fs import RealPathFs + class TrashDirVolumeReader: - def __init__(self, volumes, fs): + def __init__(self, + volumes, # type: VolumeOf + fs, # type: RealPathFs + ): self.volumes = volumes self.fs = fs diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trash_directories_finder.py trash-cli-0.23.11.10/trashcli/put/trash_directories_finder.py --- trash-cli-0.23.2.13.2/trashcli/put/trash_directories_finder.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trash_directories_finder.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,21 +1,26 @@ -from typing import Dict, List +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from typing import List -from trashcli.fstab import Volumes -from trashcli.put.candidate import Candidate -from trashcli.put.gate import SameVolumeGate, HomeFallbackGate -from trashcli.put.path_maker import AbsolutePaths, RelativePaths -from trashcli.put.security_check import NoCheck, TopTrashDirCheck -from trashcli.trash import home_trash_dir, volume_trash_dir1, volume_trash_dir2 +from trashcli.fstab.volume_of import VolumeOf +from trashcli.lib.environ import Environ +from trashcli.lib.trash_dirs import ( + volume_trash_dir1, volume_trash_dir2, home_trash_dir) +from trashcli.put.core.candidate import Candidate +from trashcli.put.core.check_type import NoCheck, TopTrashDirCheck +from trashcli.put.core.path_maker_type import PathMakerType +from trashcli.put.gate import Gate class TrashDirectoriesFinder: - def __init__(self, volumes): # type: (Volumes) -> None + def __init__(self, + volumes, # type: VolumeOf + ): self.volumes = volumes def possible_trash_directories_for(self, volume, # type: str specific_trash_dir, # type: str - environ, # type: Dict[str, str] + environ, # type: Environ uid, # type: int home_fallback, # type: bool ): # type: (...) -> List[Candidate] @@ -25,7 +30,7 @@ trash_dirs.append( Candidate(trash_dir_path=path, volume=volume, - path_maker_type=AbsolutePaths, + path_maker_type=PathMakerType.AbsolutePaths, check_type=NoCheck, gate=gate)) @@ -33,17 +38,17 @@ trash_dirs.append( Candidate(trash_dir_path=path, volume=volume, - path_maker_type=RelativePaths, + path_maker_type=PathMakerType.RelativePaths, check_type=TopTrashDirCheck, - gate=SameVolumeGate)) + gate=Gate.SameVolume)) def add_alt_top_trash_dir(path, volume): trash_dirs.append( Candidate(trash_dir_path=path, volume=volume, - path_maker_type=RelativePaths, + path_maker_type=PathMakerType.RelativePaths, check_type=NoCheck, - gate=SameVolumeGate)) + gate=Gate.SameVolume)) if specific_trash_dir: path = specific_trash_dir @@ -51,19 +56,19 @@ trash_dirs.append( Candidate(trash_dir_path=path, volume=volume, - path_maker_type=RelativePaths, + path_maker_type=PathMakerType.RelativePaths, check_type=NoCheck, - gate=SameVolumeGate)) + gate=Gate.SameVolume)) else: for path, dir_volume in home_trash_dir(environ, - self.volumes.volume_of): - add_home_trash(path, dir_volume, SameVolumeGate) + self.volumes): + add_home_trash(path, dir_volume, Gate.SameVolume) for path, dir_volume in volume_trash_dir1(volume, uid): add_top_trash_dir(path, dir_volume) for path, dir_volume in volume_trash_dir2(volume, uid): add_alt_top_trash_dir(path, dir_volume) if home_fallback: for path, dir_volume in home_trash_dir(environ, - self.volumes.volume_of): - add_home_trash(path, dir_volume, HomeFallbackGate) + self.volumes): + add_home_trash(path, dir_volume, Gate.HomeFallback) return trash_dirs diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trash_directory_for_put.py trash-cli-0.23.11.10/trashcli/put/trash_directory_for_put.py --- trash-cli-0.23.2.13.2/trashcli/put/trash_directory_for_put.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trash_directory_for_put.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -import os - -from trashcli.put.candidate import Candidate -from trashcli.put.clock import PutClock -from trashcli.put.format_trash_info import format_trashinfo -from trashcli.put.fs.fs import Fs -from trashcli.put.info_dir import InfoDir -from trashcli.put.my_logger import LogData -from trashcli.put.original_location import OriginalLocation -from trashcli.trash import path_of_backup_copy - - -class TrashDirectoryForPut: - def __init__(self, - fs, # type: Fs - info_dir, # type: InfoDir - original_location, # type: OriginalLocation - clock, # type: PutClock - ): - self.fs = fs - self.info_dir = info_dir - self.original_location = original_location - self.clock = clock - - def trash2(self, - path, - candidate, # type: Candidate - log_data, # type: LogData - ): - original_location = self.original_location.for_file( - path, candidate.path_maker_type, candidate.volume) - basename = os.path.basename(original_location) - content = format_trashinfo(original_location, self.clock.now()) - trash_info_file = self.info_dir.persist_trash_info(basename, content, - log_data, - candidate.info_dir()) - where_to_store_trashed_file = path_of_backup_copy(trash_info_file) - - try: - self.fs.move(path, where_to_store_trashed_file) - except IOError as e: - self.fs.remove_file(trash_info_file) - raise e diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trash_file_in.py trash-cli-0.23.11.10/trashcli/put/trash_file_in.py --- trash-cli-0.23.2.13.2/trashcli/put/trash_file_in.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trash_file_in.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,77 +0,0 @@ -from typing import Dict - -from trashcli.put.candidate import Candidate -from trashcli.put.dir_maker import DirMaker -from trashcli.put.fs.fs import Fs -from trashcli.put.my_logger import LogData -from trashcli.put.reporter import TrashPutReporter -from trashcli.put.security_check import SecurityCheck -from trashcli.put.trash_directory_for_put import TrashDirectoryForPut -from trashcli.put.trashee import Trashee -from trashcli.put.trashing_checker import TrashingChecker - - -class TrashFileIn: - def __init__(self, - fs, # type: Fs - reporter, # type: TrashPutReporter - trash_dir, # type: TrashDirectoryForPut - trashing_checker, # type: TrashingChecker - dir_maker, # type: DirMaker - ): - self.reporter = reporter - self.security_check = SecurityCheck(fs) - self.trash_dir = trash_dir - self.dir_maker = dir_maker - self.trashing_checker = trashing_checker - - def trash_file_in(self, - candidate, # type: Candidate - log_data, # type: LogData - environ, # type: Dict[str, str] - trashee, # type: Trashee - ): # type: (...) -> bool - trash_dir_is_secure, messages = self.security_check. \ - check_trash_dir_is_secure(candidate) - self.reporter.log_info_messages(messages, log_data) - - if not trash_dir_is_secure: - self.reporter.trash_dir_is_not_secure(candidate.norm_path(), - log_data) - return False - self.reporter.trash_dir_with_volume(candidate, log_data) - - could_be_trashed_in = self.trashing_checker.file_could_be_trashed_in( - trashee, candidate, environ) - if not could_be_trashed_in.ok: - self.reporter.log_info(could_be_trashed_in.reason, log_data) - return False - - error = self.try_trash(candidate, log_data, environ, trashee) - if error: - self.reporter.unable_to_trash_file_in_because( - trashee.path, candidate, error, log_data, - environ) - return False - - return True - - def try_trash(self, - candidate, # type: Candidate - log_data, # type : LogData - environ, # type: Dict[str, str] - trashee, # type: Trashee - ): - try: - self.dir_maker.mkdir_p(candidate.trash_dir_path, 0o700) - self.dir_maker.mkdir_p(candidate.files_dir(), 0o700) - self.dir_maker.mkdir_p(candidate.info_dir(), 0o700) - - self.trash_dir.trash2(trashee.path, candidate, log_data) - self.reporter.file_has_been_trashed_in_as(trashee.path, - candidate, - log_data, - environ) - return None - except (IOError, OSError) as error: - return error diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trash_put_cmd.py trash-cli-0.23.11.10/trashcli/put/trash_put_cmd.py --- trash-cli-0.23.2.13.2/trashcli/put/trash_put_cmd.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trash_put_cmd.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,3 +1,6 @@ +from typing import List + +from trashcli.lib.environ import Environ from trashcli.put.my_logger import LogData from trashcli.put.parser import Parser, ExitWithCode, Trash from trashcli.put.reporter import TrashPutReporter @@ -12,12 +15,16 @@ self.trash_all = trash_all self.reporter = reporter - def run(self, argv, environ, uid): + def run_put(self, + argv, # type: List[str] + environ, # type: Environ + uid, # type: int + ): # type: (...) -> int parser = Parser() parsed = parser.parse_args(argv) - if parsed.type == ExitWithCode: + if isinstance(parsed, ExitWithCode): return parsed.exit_code - elif parsed.type == Trash: + elif isinstance(parsed, Trash): program_name = parsed.program_name log_data = LogData(program_name, parsed.verbose) result = self.trash_all.trash_all(parsed.files, diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trash_result.py trash-cli-0.23.11.10/trashcli/put/trash_result.py --- trash-cli-0.23.2.13.2/trashcli/put/trash_result.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trash_result.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -class TrashResult: - def __init__(self, some_file_has_not_be_trashed): - self.some_file_has_not_be_trashed = some_file_has_not_be_trashed - - def mark_unable_to_trash_file(self): - return TrashResult(True) - - def __eq__(self, other): - return self.some_file_has_not_be_trashed == \ - other.some_file_has_not_be_trashed - - def __repr__(self): - return 'TrashResult(%s)' % self.some_file_has_not_be_trashed diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trashee.py trash-cli-0.23.11.10/trashcli/put/trashee.py --- trash-cli-0.23.2.13.2/trashcli/put/trashee.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trashee.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -from typing import NamedTuple - - -class Trashee(NamedTuple('FileToBeTrashed', [ - ('path', str), - ('volume', str) -])): - pass diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trasher.py trash-cli-0.23.11.10/trashcli/put/trasher.py --- trash-cli-0.23.2.13.2/trashcli/put/trasher.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trasher.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,15 +1,15 @@ import os -from typing import Dict - +from trashcli.lib.environ import Environ +from trashcli.put.core.trash_result import TrashResult from trashcli.put.file_trasher import FileTrasher from trashcli.put.fs.fs import Fs -from trashcli.put.fs.real_fs import RealFs from trashcli.put.my_logger import LogData -from trashcli.put.parser import mode_force, mode_interactive +from trashcli.put.parser import mode_force +from trashcli.put.parser import mode_interactive from trashcli.put.reporter import TrashPutReporter -from trashcli.put.trash_result import TrashResult -from trashcli.put.user import User, user_replied_no +from trashcli.put.user import User +from trashcli.put.user import user_replied_no class Trasher: @@ -24,18 +24,17 @@ self.reporter = reporter self.fs = fs - def trash(self, - path, - user_trash_dir, - result, # type: TrashResult - mode, - forced_volume, - home_fallback, - program_name, - log_data, # type: LogData - environ, # type: Dict[str, str] - uid, # type: int - ): + def trash_single(self, + path, + user_trash_dir, + mode, + forced_volume, + home_fallback, + program_name, + log_data, # type: LogData + environ, # type: Environ + uid, # type: int + ): """ Trash a file in the appropriate trash directory. If the file belong to the same volume of the trash home directory it @@ -53,30 +52,30 @@ if self._should_skipped_by_specs(path): self.reporter.unable_to_trash_dot_entries(path, program_name) - return result + return TrashResult.Failure if not self.fs.lexists(path): if mode == mode_force: - return result + return TrashResult.Success else: - self.reporter.unable_to_trash_file(path, log_data) - return result.mark_unable_to_trash_file() + self.reporter.unable_to_trash_file_non_existent(path, log_data) + return TrashResult.Failure if mode == mode_interactive and self.fs.is_accessible(path): reply = self.user.ask_user_about_deleting_file(program_name, path) if reply == user_replied_no: - return result + return TrashResult.Success return self.file_trasher.trash_file(path, forced_volume, user_trash_dir, home_fallback, - result, environ, uid, log_data, ) - def _should_skipped_by_specs(self, file): - basename = os.path.basename(file) + @staticmethod + def _should_skipped_by_specs(path): + basename = os.path.basename(path) return (basename == ".") or (basename == "..") diff -Nru trash-cli-0.23.2.13.2/trashcli/put/trashing_checker.py trash-cli-0.23.11.10/trashcli/put/trashing_checker.py --- trash-cli-0.23.2.13.2/trashcli/put/trashing_checker.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/trashing_checker.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -from trashcli.put.gate import Gate, ClosedGate, SameVolumeGate, HomeFallbackGate -from typing import Dict, TypeVar, Union, Type - -from trashcli.put.candidate import Candidate -from trashcli.put.gate_impl import GateCheckResult, GateImpl, ClosedGateImpl, \ - SameVolumeGateImpl, HomeFallbackGateImpl -from trashcli.put.trashee import Trashee - -Gate_ = Type[Union[ClosedGate, HomeFallbackGate, SameVolumeGate]] -GateImpl_ = Union[ClosedGateImpl, HomeFallbackGateImpl, SameVolumeGateImpl] - -class TrashingChecker: - def __init__(self, gates): # type: (Dict[Gate_, GateImpl_]) -> None - self.gates = gates - - def file_could_be_trashed_in(self, - trashee, # type: Trashee - candidate, # type: Candidate, - environ, # type: Dict[str, str] - ): # type: (...) -> GateCheckResult - gate = self.gates[candidate.gate] - - return gate.can_trash_in(trashee, candidate, environ) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/user.py trash-cli-0.23.11.10/trashcli/put/user.py --- trash-cli-0.23.2.13.2/trashcli/put/user.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/user.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,16 +1,17 @@ +from trashcli.lib.my_input import Input from trashcli.put.describer import Describer class User: def __init__(self, - my_input_func, + my_input, # type: Input describer, # type: Describer ): - self.my_input = my_input_func + self.input = my_input self.describer = describer def ask_user_about_deleting_file(self, program_name, path): - reply = self.my_input( + reply = self.input.read_input( "%s: trash %s '%s'? " % (program_name, self.describer.describe(path), path)) return parse_user_reply(reply) diff -Nru trash-cli-0.23.2.13.2/trashcli/put/volume_message_formatter.py trash-cli-0.23.11.10/trashcli/put/volume_message_formatter.py --- trash-cli-0.23.2.13.2/trashcli/put/volume_message_formatter.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/put/volume_message_formatter.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -from typing import Dict - -from trashcli.put.candidate import Candidate -from trashcli.put.trashee import Trashee - - -class VolumeMessageFormatter: - def format_msg(self, - trashee, # type: Trashee - candidate, # type: Candidate - environ, # type: Dict[str, str] - ): - formatted_dir = candidate.shrink_user(environ) - - return ( - "won't use trash dir %s because its volume (%s) in a different volume than %s (%s)" - % (formatted_dir, candidate.volume, trashee.path, - trashee.volume)) diff -Nru trash-cli-0.23.2.13.2/trashcli/py2compat.py trash-cli-0.23.11.10/trashcli/py2compat.py --- trash-cli-0.23.2.13.2/trashcli/py2compat.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/py2compat.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -try: - from urllib import quote as url_quote -except ImportError: - from urllib.parse import quote as url_quote -url_quote = url_quote diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/__init__.py trash-cli-0.23.11.10/trashcli/restore/__init__.py --- trash-cli-0.23.2.13.2/trashcli/restore/__init__.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/__init__.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,32 +0,0 @@ -import sys - -from .file_system import RestoreFileSystem -from .restore_cmd import RestoreCmd -from .trash_directories import make_trash_directories, TrashDirectory -from .trashed_file import TrashedFiles -from .. import trash -from ..fs import contents_of -from ..lib.my_input import my_input -from ..list_mount_points import os_mount_points -from .range import Range -from .sequences import Sequences -from .single import Single -from .my_range import my_range - - -def main(): - trash_directories = make_trash_directories() - logger = trash.logger - trashed_files = TrashedFiles(logger, - trash_directories, - TrashDirectory(), - contents_of) - RestoreCmd( - stdout=sys.stdout, - stderr=sys.stderr, - exit=sys.exit, - input=my_input, - trashed_files=trashed_files, - mount_points=os_mount_points, - fs=RestoreFileSystem() - ).run(sys.argv) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/args.py trash-cli-0.23.11.10/trashcli/restore/args.py --- trash-cli-0.23.2.13.2/trashcli/restore/args.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/args.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,23 @@ +from enum import Enum +from typing import NamedTuple, Optional + +from trashcli.lib.enum_repr import repr_for_enum + + +class Sort(Enum): + ByDate = "ByDate" + ByPath = "ByPath" + DoNot = "DoNot" + + def __repr__(self): + return repr_for_enum(self) + + +class RunRestoreArgs( + NamedTuple('RunRestoreArgs', [ + ('path', str), + ('sort', Sort), + ('trash_dir', Optional[str]), + ('overwrite', bool), + ])): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/file_system.py trash-cli-0.23.11.10/trashcli/restore/file_system.py --- trash-cli-0.23.2.13.2/trashcli/restore/file_system.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/file_system.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,15 +1,63 @@ import os +from abc import ABCMeta, abstractmethod +from trashcli.compat import Protocol + +import six from trashcli import fs +from trashcli.fs import FsMethods, RealListFilesInDir, ListFilesInDir, \ + RealContentsOf -class RestoreFileSystem: - def __init__(self, default_cur_dir=None): - self.default_cur_dir = default_cur_dir +class FileReader(Protocol): + @abstractmethod + def contents_of(self, path): + raise NotImplementedError() + + +class RealFileReader(RealContentsOf, FileReader): + pass + + +class FakeFileReader(FileReader): + def __init__(self, contents=None): + self.contents = contents + + def set_content(self, contents): + self.contents = contents + + def contents_of(self, path): + return self.contents + +@six.add_metaclass(ABCMeta) +class RestoreReadFileSystem: + @abstractmethod + def path_exists(self, path): # type: (str) -> bool + raise NotImplementedError() + + +class RealRestoreReadFileSystem(RestoreReadFileSystem): def path_exists(self, path): return os.path.exists(path) + +@six.add_metaclass(ABCMeta) +class RestoreWriteFileSystem: + @abstractmethod + def mkdirs(self, path): # type: (str) -> None + raise NotImplementedError() + + @abstractmethod + def move(self, path, dest): # type: (str, str) -> None + raise NotImplementedError() + + @abstractmethod + def remove_file(self, path): # type: (str) -> None + raise NotImplementedError() + + +class RealRestoreWriteFileSystem(RestoreWriteFileSystem): def mkdirs(self, path): return fs.mkdirs(path) @@ -19,15 +67,20 @@ def remove_file(self, path): return fs.remove_file(path) + +@six.add_metaclass(ABCMeta) +class ReadCwd: + @abstractmethod + def getcwd_as_realpath(self): # type: () -> str + raise NotImplementedError() + + +class RealReadCwd(ReadCwd): def getcwd_as_realpath(self): - if type(self.default_cur_dir) == str: - return self.default_cur_dir - if self.default_cur_dir: - return self.default_cur_dir() return os.path.realpath(os.curdir) -class FakeRestoreFileSystem(RestoreFileSystem): +class FakeReadCwd(ReadCwd): def __init__(self, default_cur_dir=None): self.default_cur_dir = default_cur_dir @@ -38,5 +91,9 @@ return self.default_cur_dir -def getcwd_as_realpath(): - return os.path.realpath(os.curdir) +class ListingFileSystem(ListFilesInDir, Protocol): + pass + + +class RealListingFileSystem(ListingFileSystem, RealListFilesInDir): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/handler.py trash-cli-0.23.11.10/trashcli/restore/handler.py --- trash-cli-0.23.2.13.2/trashcli/restore/handler.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/handler.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,52 @@ +from __future__ import print_function +from __future__ import unicode_literals + +from typing import List + +from trashcli.lib.my_input import Input +from trashcli.restore.file_system import ReadCwd +from trashcli.restore.output import Output +from trashcli.restore.output_recorder import OutputRecorder +from trashcli.restore.restore_asking_the_user import RestoreAskingTheUser +from trashcli.restore.restorer import Restorer +from trashcli.restore.run_restore_action import Handler +from trashcli.restore.trashed_file import TrashedFile + + +class HandlerImpl(Handler): + def __init__(self, + input, # type: Input + cwd, # type: ReadCwd + restorer, # type: Restorer + output, # type: Output + ): + self.input = input + self.cwd = cwd + self.restorer = restorer + self.output = output + + def handle_trashed_files(self, + trashed_files, # type: List[TrashedFile] + overwrite, # type: bool + ): + if not trashed_files: + self.report_no_files_found(self.cwd.getcwd_as_realpath()) + else: + for i, trashed_file in enumerate(trashed_files): + self.output.println("%4d %s %s" % (i, + trashed_file.deletion_date, + trashed_file.original_location)) + self.restore_asking_the_user(trashed_files, overwrite) + + def restore_asking_the_user(self, trashed_files, overwrite=False): + my_output = OutputRecorder() + restore_asking_the_user = RestoreAskingTheUser(self.input, + self.restorer, + my_output) + restore_asking_the_user.restore_asking_the_user(trashed_files, + overwrite) + my_output.apply_to(self.output) + + def report_no_files_found(self, directory): # type: (str) -> None + self.output.println( + "No files trashed from current dir ('%s')" % directory) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/index.py trash-cli-0.23.11.10/trashcli/restore/index.py --- trash-cli-0.23.2.13.2/trashcli/restore/index.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/index.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,6 @@ +from typing import Union + +from trashcli.restore.range import Range +from trashcli.restore.single import Single + +Sequence = Union[Range, Single] diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/info_dir_searcher.py trash-cli-0.23.11.10/trashcli/restore/info_dir_searcher.py --- trash-cli-0.23.2.13.2/trashcli/restore/info_dir_searcher.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/info_dir_searcher.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,29 @@ +from typing import NamedTuple, Iterable, Optional + +from trashcli.restore.info_files import InfoFiles +from trashcli.restore.trash_directories import TrashDirectories + + +class InfoDirSearcher: + def __init__(self, + trash_directories, # type: TrashDirectories + info_files, # type: InfoFiles + ): + self.trash_directories = trash_directories + self.info_files = info_files + + def all_file_in_info_dir(self, + trash_dir_from_cli, # type: Optional[str] + ): # type: (...) -> Iterable[FileFound] + for trash_dir_path, volume in self.trash_directories.list_trash_dirs( + trash_dir_from_cli): + for type, path in self.info_files.all_info_files(trash_dir_path): + yield FileFound(type, path, volume) + + +class FileFound(NamedTuple('Info', [ + ('type', 'str'), + ('path', 'str'), + ('volume', 'str'), +])): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/info_files.py trash-cli-0.23.11.10/trashcli/restore/info_files.py --- trash-cli-0.23.2.13.2/trashcli/restore/info_files.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/info_files.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,22 @@ +import os + +from trashcli.restore.file_system import ListingFileSystem + + +class InfoFiles: + def __init__(self, + fs, # type: ListingFileSystem + ): + self.fs = fs + + def all_info_files(self, path): + norm_path = os.path.normpath(path) + info_dir = os.path.join(norm_path, 'info') + try: + for info_file in self.fs.list_files_in_dir(info_dir): + if not os.path.basename(info_file).endswith('.trashinfo'): + yield ('non_trashinfo', info_file) + else: + yield ('trashinfo', info_file) + except OSError: # when directory does not exist + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/main.py trash-cli-0.23.11.10/trashcli/restore/main.py --- trash-cli-0.23.2.13.2/trashcli/restore/main.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/main.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,39 @@ +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +import os +import sys + +import trashcli.trash +from .file_system import RealRestoreReadFileSystem, \ + RealRestoreWriteFileSystem, RealReadCwd, RealFileReader, \ + RealListingFileSystem +from .info_dir_searcher import InfoDirSearcher +from .info_files import InfoFiles +from .restore_cmd import RestoreCmd +from .trash_directories import TrashDirectoriesImpl +from .trashed_files import TrashedFiles +from ..fstab.volumes import RealVolumes +from ..lib.logger import my_logger +from ..lib.my_input import RealInput + + +def main(): + info_files = InfoFiles(RealListingFileSystem()) + volumes = RealVolumes() + trash_directories = TrashDirectoriesImpl(volumes, + os.getuid(), + os.environ) + searcher = InfoDirSearcher(trash_directories, info_files) + trashed_files = TrashedFiles(my_logger, + RealFileReader(), + searcher) + RestoreCmd.make( + stdout=sys.stdout, + stderr=sys.stderr, + exit=sys.exit, + input=RealInput(), + version=trashcli.trash.version, + trashed_files=trashed_files, + read_fs=RealRestoreReadFileSystem(), + write_fs=RealRestoreWriteFileSystem(), + read_cwd=RealReadCwd() + ).run(sys.argv) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/my_range.py trash-cli-0.23.11.10/trashcli/restore/my_range.py --- trash-cli-0.23.2.13.2/trashcli/restore/my_range.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/my_range.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -try: - my_range = xrange -except NameError: - my_range = range diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/output.py trash-cli-0.23.11.10/trashcli/restore/output.py --- trash-cli-0.23.2.13.2/trashcli/restore/output.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/output.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,26 @@ +from abc import ABCMeta, abstractmethod + +import six + +from trashcli.restore.output_event import OutputEvent + + +@six.add_metaclass(ABCMeta) +class Output: + @abstractmethod + def quit(self): + raise NotImplementedError + + @abstractmethod + def die(self, msg): + raise NotImplementedError + + @abstractmethod + def println(self, msg): + raise NotImplementedError + + @abstractmethod + def append_event(self, + event, # type: OutputEvent + ): + raise NotImplementedError diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/output_event.py trash-cli-0.23.11.10/trashcli/restore/output_event.py --- trash-cli-0.23.2.13.2/trashcli/restore/output_event.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/output_event.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,7 @@ +from typing import NamedTuple, Union + +Quit = NamedTuple('Quit', []) +Die = NamedTuple('Die', [('msg', Union[str, Exception])]) +Println = NamedTuple('Println', [('msg', str)]) +Exiting = NamedTuple('Exiting', []) +OutputEvent = Union[Quit, Die, Println, Exiting] diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/output_recorder.py trash-cli-0.23.11.10/trashcli/restore/output_recorder.py --- trash-cli-0.23.2.13.2/trashcli/restore/output_recorder.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/output_recorder.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,28 @@ +from typing import List + +from trashcli.restore.output import Output +from trashcli.restore.output_event import Quit, Die, Println, OutputEvent + + +class OutputRecorder(Output): + def __init__(self): # type: (...) -> None + self.events = [] # type: List[OutputEvent] + + def quit(self): # type: () -> None + self.append_event(Quit()) + + def die(self, msg): # type: (str) -> None + self.append_event(Die(msg)) + + def println(self, msg): # type: (str) -> None + self.append_event(Println(msg)) + + def append_event(self, + event, # type: OutputEvent + ): # type: (...) -> None + self.events.append(event) + + def apply_to(self, output, # type: Output + ): # type: (...) -> None + for event in self.events: + output.append_event(event) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/parse_restore_args.py trash-cli-0.23.11.10/trashcli/restore/parse_restore_args.py --- trash-cli-0.23.2.13.2/trashcli/restore/parse_restore_args.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/parse_restore_args.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -import os - -from trashcli.shell_completion import add_argument_to, TRASH_FILES, TRASH_DIRS - - -class Command: - PrintVersion = "Command.PrintVersion" - RunRestore = "Command.RunRestore" - - -def parse_restore_args(sys_argv, curdir): - import argparse - - parser = argparse.ArgumentParser( - description='Restores from trash chosen file', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - add_argument_to(parser) - parser.add_argument('path', - default="", nargs='?', - help='Restore files from given path instead of current ' - 'directory' - ).complete = TRASH_FILES - parser.add_argument('--sort', - choices=['date', 'path', 'none'], - default='date', - help='Sort list of restore candidates by given field') - parser.add_argument('--trash-dir', - action='store', - dest='trash_dir', - help=argparse.SUPPRESS - ).complete = TRASH_DIRS - parser.add_argument('--version', action='store_true', default=False) - - parser.add_argument('--overwrite', - action='store_true', - default=False, - help='Overwrite existing files with files coming out of the trash') - - parsed = parser.parse_args(sys_argv[1:]) - - if parsed.version: - return Command.PrintVersion, None - else: - path = os.path.normpath(os.path.join(curdir + os.path.sep, parsed.path)) - return Command.RunRestore, {'path': path, - 'sort': parsed.sort, - 'trash_dir': parsed.trash_dir, - 'overwrite': parsed.overwrite} diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/range.py trash-cli-0.23.11.10/trashcli/restore/range.py --- trash-cli-0.23.2.13.2/trashcli/restore/range.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/range.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,4 +1,4 @@ -from trashcli.restore.my_range import my_range +from six.moves import range class Range: @@ -16,7 +16,7 @@ return True def __iter__(self): - return iter(my_range(self.start, self.stop + 1)) + return iter(range(self.start, self.stop + 1)) def __repr__(self): return "Range(%s, %s)" % (self.start, self.stop) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/real_output.py trash-cli-0.23.11.10/trashcli/restore/real_output.py --- trash-cli-0.23.2.13.2/trashcli/restore/real_output.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/real_output.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,41 @@ +from __future__ import print_function + +import six + +from trashcli.restore.output import Output +from trashcli.restore.output_event import Println, Die, Quit, Exiting, \ + OutputEvent + + +class RealOutput(Output): + def __init__(self, stdout, stderr, exit): + self.stdout = stdout + self.stderr = stderr + self.exit = exit + + def quit(self): + self.die('') + + def printerr(self, msg): + print(six.text_type(msg), file=self.stderr) + + def println(self, line): + print(six.text_type(line), file=self.stdout) + + def die(self, error): + self.printerr(error) + self.exit(1) + + def append_event(self, + event, # type: OutputEvent + ): + if isinstance(event, Println): + self.println(event.msg) + elif isinstance(event, Die): + self.die(event.msg) + elif isinstance(event, Quit): + self.quit() + elif isinstance(event, Exiting): + self.println("Exiting") + else: + raise Exception("Unknown call %s" % event) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/restore_arg_parser.py trash-cli-0.23.11.10/trashcli/restore/restore_arg_parser.py --- trash-cli-0.23.2.13.2/trashcli/restore/restore_arg_parser.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/restore_arg_parser.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,64 @@ +import os +from typing import Union, List, cast + +from trashcli.lib.print_version import PrintVersionArgs +from trashcli.restore.args import RunRestoreArgs, Sort +from trashcli.shell_completion import add_argument_to, TRASH_FILES, TRASH_DIRS, \ + complete_with + +Command = Union[PrintVersionArgs, RunRestoreArgs] + + +class RestoreArgParser: + def parse_restore_args(self, + sys_argv, # type: List[str] + curdir, # type: str + ): # type: (...) -> Command + import argparse + + parser = argparse.ArgumentParser( + description='Restores from trash chosen file', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + add_argument_to(parser) + complete_with( + TRASH_FILES, + parser.add_argument('path', + default="", nargs='?', + help='Restore files from given path instead of current ' + 'directory' + ) + ) + parser.add_argument('--sort', + choices=['date', 'path', 'none'], + default='date', + help='Sort list of restore candidates by given field') + complete_with( + TRASH_DIRS, + parser.add_argument('--trash-dir', + action='store', + dest='trash_dir', + help=argparse.SUPPRESS + )) + parser.add_argument('--version', action='store_true', default=False) + + parser.add_argument('--overwrite', + action='store_true', + default=False, + help='Overwrite existing files with files coming out of the trash') + + parsed = parser.parse_args(sys_argv[1:]) + + if parsed.version: + return PrintVersionArgs(argv0=sys_argv[0]) + else: + path = os.path.normpath( + os.path.join(curdir + os.path.sep, parsed.path)) + + return RunRestoreArgs(path=path, + sort=cast(Sort, { + 'path': Sort.ByPath, + 'date': Sort.ByDate, + 'none': Sort.DoNot + }[parsed.sort]), + trash_dir=parsed.trash_dir, + overwrite=parsed.overwrite) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/restore_asking_the_user.py trash-cli-0.23.11.10/trashcli/restore/restore_asking_the_user.py --- trash-cli-0.23.2.13.2/trashcli/restore/restore_asking_the_user.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/restore_asking_the_user.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,47 +1,151 @@ +from typing import TypeVar, Generic, List, NamedTuple, Callable + +from six.moves import range + +from trashcli.lib.my_input import Input +from trashcli.restore.index import Sequence +from trashcli.restore.output import Output +from trashcli.restore.output_event import Die, Exiting, OutputEvent, Quit from trashcli.restore.range import Range -from trashcli.restore.single import Single from trashcli.restore.sequences import Sequences -from trashcli.restore.my_range import my_range +from trashcli.restore.single import Single +from trashcli.restore.trashed_file import TrashedFile + +SelectedFiles = NamedTuple('SelectedFiles', [ + ('files_to_restore', List[TrashedFile]), + ('overwrite', bool), +]) + +Context = NamedTuple('Context', [ + ('trashed_files', List[TrashedFile]), + ('overwrite', bool), +]) + +InputRead = NamedTuple('InputRead', [ + ('user_input', str), + ('trashed_files', List[TrashedFile]), + ('overwrite', bool), +]) class RestoreAskingTheUser(object): - def __init__(self, input, println, restore, die): + def __init__(self, + input, # type: Input + restorer, + output, # type: Output + ): self.input = input - self.println = println - self.restore = restore - self.die = die + self.restorer = restorer + self.output = output - def restore_asking_the_user(self, trashed_files, overwrite=False): + def read_user_input(self, + args, # type: Context + ): # type: (...) -> Either[OutputEvent, InputRead] try: - user_input = self.input( - "What file to restore [0..%d]: " % (len(trashed_files) - 1)) + user_input = self.input.read_input( + "What file to restore [0..%d]: " % ( + len(args.trashed_files) - 1)) + except KeyboardInterrupt: - return self.die("") + return Left(Quit()) except EOFError: - return self.die("") - if user_input == "": - self.println("Exiting") + return Left(Quit()) else: - try: - sequences = parse_indexes(user_input, len(trashed_files)) - except InvalidEntry as e: - self.die("Invalid entry: %s" % e) + if user_input == "": + return Left(Exiting()) else: - try: - for index in sequences.all_indexes(): - trashed_file = trashed_files[index] - self.restore(trashed_file, overwrite) - except IOError as e: - self.die(e) + return Right( + InputRead(user_input, args.trashed_files, args.overwrite)) + + def restore_asking_the_user(self, trashed_files, overwrite): + input = Right(Context(trashed_files, overwrite)) + compose(input, [ + self.read_user_input, + trashed_files_to_restore, + self.restore_selected_files, + ]).on_error( + lambda error: self.output.append_event(error)) + + def restore_selected_files(self, + selected_files, # type: SelectedFiles + ): # type: (...) -> Either[Die, None] + try: + for trashed_file in selected_files.files_to_restore: + self.restorer.restore_trashed_file(trashed_file, + selected_files.overwrite) + return Right(None) + except IOError as e: + return Left(Die(e)) + + +Error = TypeVar('Error') +Value = TypeVar('Value') + + +def compose(input, funcs): + for f in funcs: + input = input.apply(f) + return input + + +class Either(Generic[Error, Value]): + def apply(self, + f): # type: (Callable[[Value], Either[Error, OutputValue]]) -> Either[Error, OutputValue] + raise NotImplementedError() + + +class Left(Either, Generic[Error, Value]): + def __init__(self, + error, # type: Error + ): + self.error = error + + def apply(self, + f): # type: (Callable[[Value], Either[Error, OutputValue]]) -> Either[Error, OutputValue] + return self + + def on_error(self, f): + return f(self.error) + + +OutputValue = TypeVar('OutputValue') + + +class Right(Either[Error, Value]): + def __init__(self, value, # type: Value + ): + self.value = value + + def apply(self, + f): # type: (Callable[[Value], Either[Error, OutputValue]]) -> Either[Error, OutputValue] + return f(self.value) + + def on_error(self, f): + return self + + +def trashed_files_to_restore(input_read, # type: InputRead + ): # type: (...) -> Either[Die, SelectedFiles] + try: + sequences = parse_indexes(input_read.user_input, + len(input_read.trashed_files)) + file_to_restore = [input_read.trashed_files[index] for index in + sequences.all_indexes()] + selected_files = SelectedFiles(file_to_restore, input_read.overwrite) + return Right(selected_files) + except InvalidEntry as e: + return Left(Die("Invalid entry: %s" % e)) class InvalidEntry(Exception): pass -def parse_indexes(user_input, len_trashed_files): +def parse_indexes(user_input, # type: str + len_trashed_files, # type: int + ): # type: (...) -> Sequences indexes = user_input.split(',') - sequences = [] + sequences = [] # type: List[Sequence] for index in indexes: if "-" in index: first, last = index.split("-", 2) @@ -50,10 +154,10 @@ split = list(map(parse_int_index, (first, last))) sequences.append(Range(split[0], split[1])) else: - index = parse_int_index(index) - sequences.append(Single(index)) + int_index = parse_int_index(index) + sequences.append(Single(int_index)) result = Sequences(sequences) - acceptable_values = my_range(0, len_trashed_files) + acceptable_values = range(0, len_trashed_files) for index in result.all_indexes(): if not index in acceptable_values: raise InvalidEntry( @@ -62,7 +166,7 @@ return result -def parse_int_index(text): +def parse_int_index(text): # type: (str) -> int try: return int(text) except ValueError: diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/restore_cmd.py trash-cli-0.23.11.10/trashcli/restore/restore_cmd.py --- trash-cli-0.23.2.13.2/trashcli/restore/restore_cmd.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/restore_cmd.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,112 +1,55 @@ -import os +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from typing import TextIO, Callable -from trashcli.restore.restore_asking_the_user import RestoreAskingTheUser -from trashcli.restore.parse_restore_args import Command, parse_restore_args -from trashcli.trash import version, print_version - - -class Restorer(object): - def __init__(self, fs): - self.fs = fs - - def restore_trashed_file(self, trashed_file, overwrite=False): - """ - If overwrite is enabled, then the restore functionality will overwrite an existing file - """ - restore(trashed_file, self.fs, overwrite) - - -def original_location_matches_path(trashed_file_original_location, path): - if path == os.path.sep: - return True - if trashed_file_original_location.startswith(path + os.path.sep): - return True - return trashed_file_original_location == path +from trashcli.lib.my_input import Input +from trashcli.lib.print_version import PrintVersionAction, PrintVersionArgs +from trashcli.restore.args import RunRestoreArgs +from trashcli.restore.file_system import RestoreReadFileSystem, \ + RestoreWriteFileSystem, ReadCwd +from trashcli.restore.handler import HandlerImpl +from trashcli.restore.real_output import RealOutput +from trashcli.restore.restore_arg_parser import RestoreArgParser +from trashcli.restore.restorer import Restorer +from trashcli.restore.run_restore_action import RunRestoreAction, Handler +from trashcli.restore.trashed_files import TrashedFiles class RestoreCmd(object): - def __init__(self, stdout, stderr, exit, input, version=version, - trashed_files=None, mount_points=None, fs=None): - self.out = stdout - self.err = stderr - self.exit = exit - self.input = input - self.version = version - self.fs = fs - self.trashed_files = trashed_files - self.mount_points = mount_points + @staticmethod + def make(stdout, # type: TextIO + stderr, # type: TextIO + exit, # type: Callable[[int], None] + input, # type: Input + version, # type: str + trashed_files, # type: TrashedFiles + read_fs, # type: RestoreReadFileSystem + write_fs, # type: RestoreWriteFileSystem + read_cwd, # type: ReadCwd + ): # type: (...) -> RestoreCmd + restorer = Restorer(read_fs, write_fs) + output = RealOutput(stdout, stderr, exit) + handler = HandlerImpl(input, read_cwd, restorer, output) + return RestoreCmd(stdout, version, trashed_files, read_cwd, handler) + + def __init__(self, + stdout, # type: TextIO + version, # type: str + trashed_files, # type: TrashedFiles + read_cwd, # type: ReadCwd + handler, # type: Handler + ): + self.read_cwd = read_cwd + self.parser = RestoreArgParser() + self.run_restore_action = RunRestoreAction(handler, + trashed_files) + self.print_version_action = PrintVersionAction(stdout, + version) def run(self, argv): - cmd, args = parse_restore_args(argv, self.fs.getcwd_as_realpath()) - if cmd == Command.PrintVersion: - command = os.path.basename(argv[0]) - print_version(self.out, command, self.version) - return - elif cmd == Command.RunRestore: - trash_dir_from_cli = args['trash_dir'] - trashed_files = list(self.all_files_trashed_from_path( - args['path'], trash_dir_from_cli)) - if args['sort'] == 'path': - trashed_files = sorted(trashed_files, - key=lambda x: x.original_location + str( - x.deletion_date)) - elif args['sort'] == 'date': - trashed_files = sorted(trashed_files, - key=lambda x: x.deletion_date) - - self.handle_trashed_files(trashed_files, args['overwrite']) - - def handle_trashed_files(self, trashed_files, overwrite=False): - if not trashed_files: - self.report_no_files_found() - else: - for i, trashedfile in enumerate(trashed_files): - self.println("%4d %s %s" % (i, - trashedfile.deletion_date, - trashedfile.original_location)) - self.restore_asking_the_user(trashed_files, overwrite) - - def restore_asking_the_user(self, trashed_files, overwrite=False): - restore_asking_the_user = RestoreAskingTheUser(self.input, - self.println, - self.restore, - self.die) - restore_asking_the_user.restore_asking_the_user(trashed_files, - overwrite) - - def die(self, error): - self.printerr(error) - self.exit(1) - - def restore(self, trashed_file, overwrite=False): - restorer = Restorer(self.fs) - restorer.restore_trashed_file(trashed_file, overwrite) - - def all_files_trashed_from_path(self, path, trash_dir_from_cli): - for trashed_file in self.trashed_files.all_trashed_files( - self.mount_points(), trash_dir_from_cli): - if original_location_matches_path(trashed_file.original_location, - path): - yield trashed_file - - def report_no_files_found(self): - self.println("No files trashed from current dir ('%s')" % self.fs.getcwd_as_realpath()) - - def println(self, line): - self.out.write(line + '\n') - - def printerr(self, msg): - self.err.write('%s\n' % msg) - - -def restore(trashed_file, fs, overwrite=False): - if not overwrite and fs.path_exists(trashed_file.original_location): - raise IOError( - 'Refusing to overwrite existing file "%s".' % os.path.basename( - trashed_file.original_location)) - else: - parent = os.path.dirname(trashed_file.original_location) - fs.mkdirs(parent) + args = self.parser.parse_restore_args(argv, + self.read_cwd.getcwd_as_realpath()) - fs.move(trashed_file.original_file, trashed_file.original_location) - fs.remove_file(trashed_file.info_file) + if isinstance(args, RunRestoreArgs): + self.run_restore_action.run_action(args) + elif isinstance(args, PrintVersionArgs): + self.print_version_action.run_action(args) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/restorer.py trash-cli-0.23.11.10/trashcli/restore/restorer.py --- trash-cli-0.23.2.13.2/trashcli/restore/restorer.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/restorer.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,32 @@ +import os + +from trashcli.restore.file_system import RestoreWriteFileSystem, \ + RestoreReadFileSystem +from trashcli.restore.trashed_file import TrashedFile + + +class Restorer: + def __init__(self, + read_fs, # type: RestoreReadFileSystem + write_fs, # type: RestoreWriteFileSystem + ): + self.read_fs = read_fs + self.write_fs = write_fs + + def restore_trashed_file(self, + trashed_file, # type: TrashedFile + overwrite, # type: bool + ): + """ + If overwrite is enabled, then the restore functionality will overwrite an existing file + """ + if not overwrite and self.read_fs.path_exists(trashed_file.original_location): + raise IOError( + 'Refusing to overwrite existing file "%s".' % os.path.basename( + trashed_file.original_location)) + else: + parent = os.path.dirname(trashed_file.original_location) + self.write_fs.mkdirs(parent) + + self.write_fs.move(trashed_file.original_file, trashed_file.original_location) + self.write_fs.remove_file(trashed_file.info_file) diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/run_restore_action.py trash-cli-0.23.11.10/trashcli/restore/run_restore_action.py --- trash-cli-0.23.2.13.2/trashcli/restore/run_restore_action.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/run_restore_action.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,56 @@ +import os +from abc import ABCMeta, abstractmethod + +import six +from typing import Optional, Iterable + +from trashcli.restore.args import RunRestoreArgs +from trashcli.restore.sort_method import sort_files +from trashcli.restore.trashed_file import TrashedFile +from trashcli.restore.trashed_files import TrashedFiles + + +class RunRestoreAction: + def __init__(self, + handler, # type: 'Handler' + trashed_files, # type: TrashedFiles + ): + self.handler = handler + self.trashed_files = trashed_files + + def run_action(self, args, # type: RunRestoreArgs + ): # type: (...) -> None + trashed_files = self.all_files_trashed_from_path(args.path, + args.trash_dir) + + trashed_files = sort_files(args.sort, trashed_files) + + self.handler.handle_trashed_files(trashed_files, + args.overwrite) + + def all_files_trashed_from_path(self, + path, # type: str + trash_dir_from_cli, # type: Optional[str] + ): # type: (...) -> Iterable[TrashedFile] + for trashed_file in self.trashed_files.all_trashed_files( + trash_dir_from_cli): + if trashed_file.original_location_matches_path(path): + yield trashed_file + + +@six.add_metaclass(ABCMeta) +class Handler: + @abstractmethod + def handle_trashed_files(self, + trashed_files, + overwrite, # type: bool + ): + raise NotImplementedError() + + +def original_location_matches_path(trashed_file_original_location, path): + if path == os.path.sep: + return True + if trashed_file_original_location.startswith(path + os.path.sep): + return True + return trashed_file_original_location == path diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/sequences.py trash-cli-0.23.11.10/trashcli/restore/sequences.py --- trash-cli-0.23.2.13.2/trashcli/restore/sequences.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/sequences.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,18 +1,12 @@ -class Sequences: - def __init__(self, sequences): - self.sequences = sequences +from typing import NamedTuple, List - def __repr__(self): - return "Sequences(%s)" % repr(self.sequences) +from trashcli.restore.index import Sequence + +class Sequences(NamedTuple('Sequences', [ + ('sequences', List[Sequence]), +])): def all_indexes(self): for sequence in self.sequences: for index in sequence: yield index - - def __eq__(self, other): - if type(other) != type(self): - return False - if self.sequences != other.sequences: - return False - return True diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/single.py trash-cli-0.23.11.10/trashcli/restore/single.py --- trash-cli-0.23.2.13.2/trashcli/restore/single.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/single.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,16 +1,7 @@ -class Single: - def __init__(self, index): - self.index = index +from typing import NamedTuple - def __eq__(self, other): - if type(other) != type(self): - return False - if self.index != other.index: - return False - return True - def __iter__(self): - return iter([self.index]) - - def __repr__(self): - return "Single(%s)" % self.index +class Single(NamedTuple('Single', [ + ('index', int), +])): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/sort_method.py trash-cli-0.23.11.10/trashcli/restore/sort_method.py --- trash-cli-0.23.2.13.2/trashcli/restore/sort_method.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/sort_method.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,47 @@ +from abc import abstractmethod +from typing import Callable, Any, Iterable + +from trashcli.compat import Protocol +from trashcli.restore.args import Sort +from trashcli.restore.trashed_file import TrashedFile + + +def sort_files(sort, # type: Sort + trashed_files, # type: Iterable[TrashedFile] + ): # type: (...) -> Iterable[TrashedFile] + return sorter_for(sort).sort_files(trashed_files) + + +class Sorter(Protocol): + @abstractmethod + def sort_files(self, trashed_files, # type: Iterable[TrashedFile] + ): # type: (...) -> Iterable[TrashedFile] + raise NotImplementedError() + + +class NoSorter(Sorter): + def sort_files(self, trashed_files, # type: Iterable[TrashedFile] + ): # type: (...) -> Iterable[TrashedFile] + return trashed_files + + +class SortFunction(Sorter): + def __init__(self, + sort_func): # type: (Callable[[TrashedFile], Any]) -> None + self.sort_func = sort_func + + def sort_files(self, trashed_files, # type: Iterable[TrashedFile] + ): # type: (...) -> Iterable[TrashedFile] + return sorted(trashed_files, key=self.sort_func) + + +def sorter_for(sort, # type: Sort + ): # type (...) -> Sorter + + path_ranking = lambda x: x.original_location + str(x.deletion_date) + date_rankking = lambda x: x.deletion_date + return { + Sort.ByPath: SortFunction(path_ranking), + Sort.ByDate: SortFunction(date_rankking), + Sort.DoNot: NoSorter, + }[sort] diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/trash_directories.py trash-cli-0.23.11.10/trashcli/restore/trash_directories.py --- trash-cli-0.23.2.13.2/trashcli/restore/trash_directories.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/trash_directories.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,52 +1,74 @@ -import os +# Copyright (C) 2007-2023 Andrea Francia Trivolzio(PV) Italy +from abc import abstractmethod, ABCMeta -from trashcli.fs import list_files_in_dir -from trashcli.fstab import volume_of -from trashcli.trash import home_trash_dir, volume_trash_dir1, volume_trash_dir2 +import six +from typing import Optional + +from trashcli.fstab.volume_of import VolumeOf +from trashcli.fstab.volumes import Volumes +from trashcli.lib.environ import Environ +from trashcli.lib.trash_dirs import ( + volume_trash_dir1, volume_trash_dir2, home_trash_dir) + + +@six.add_metaclass(ABCMeta) +class TrashDirectories: + @abstractmethod + def list_trash_dirs(self, trash_dir_from_cli): + raise NotImplementedError() + + +class TrashDirectoriesImpl(TrashDirectories): + def __init__(self, + volumes, # type: Volumes + uid, # type: int + environ, + ): + trash_directories1 = TrashDirectories1(volumes, uid, environ) + self.trash_directories2 = TrashDirectories2(volumes, + trash_directories1) + + def list_trash_dirs(self, + trash_dir_from_cli, # type: Optional[str] + ): + return self.trash_directories2.trash_directories_or_user( + trash_dir_from_cli) class TrashDirectories2: - def __init__(self, volume_of, trash_directories): + def __init__(self, + volume_of, # type: VolumeOf + trash_directories, # type: TrashDirectories1 + ): self.volume_of = volume_of self.trash_directories = trash_directories - def trash_directories_or_user(self, volumes, trash_dir_from_cli): + def trash_directories_or_user(self, + trash_dir_from_cli, # type: Optional[str] + ): if trash_dir_from_cli: - return [(trash_dir_from_cli, self.volume_of(trash_dir_from_cli))] - return self.trash_directories.all_trash_directories(volumes) - + return [(trash_dir_from_cli, + self.volume_of.volume_of(trash_dir_from_cli))] + return self.trash_directories.all_trash_directories() -def make_trash_directories(): - trash_directories = TrashDirectories(volume_of, os.getuid(), os.environ) - return TrashDirectories2(volume_of, trash_directories) -class TrashDirectories: - def __init__(self, volume_of, uid, environ): - self.volume_of = volume_of +class TrashDirectories1: + def __init__(self, + volumes, # type: Volumes + uid, # type: int + environ, # type: Environ + ): + self.volumes = volumes self.uid = uid self.environ = environ - def all_trash_directories(self, volumes): - for path1, volume1 in home_trash_dir(self.environ, self.volume_of): + def all_trash_directories(self): + volumes_to_check = self.volumes.list_mount_points() + for path1, volume1 in home_trash_dir(self.environ, self.volumes): yield path1, volume1 - for volume in volumes: + for volume in volumes_to_check: for path1, volume1 in volume_trash_dir1(volume, self.uid): yield path1, volume1 for path1, volume1 in volume_trash_dir2(volume, self.uid): yield path1, volume1 - - -class TrashDirectory: - - def all_info_files(self, path): - norm_path = os.path.normpath(path) - info_dir = os.path.join(norm_path, 'info') - try: - for info_file in list_files_in_dir(info_dir): - if not os.path.basename(info_file).endswith('.trashinfo'): - yield ('non_trashinfo', info_file) - else: - yield ('trashinfo', info_file) - except OSError: # when directory does not exist - pass diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/trashed_file.py trash-cli-0.23.11.10/trashcli/restore/trashed_file.py --- trash-cli-0.23.2.13.2/trashcli/restore/trashed_file.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/trashed_file.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,49 +1,15 @@ -from logging import Logger +import datetime +import os +from typing import NamedTuple, Optional -from trashcli.trash import parse_original_location, parse_deletion_date, \ - path_of_backup_copy - -class TrashedFiles: - def __init__(self, - logger, # type: Logger - trash_directories, - trash_directory, - contents_of): - self.logger = logger - self.trash_directories = trash_directories - self.trash_directory = trash_directory - self.contents_of = contents_of - - def all_trashed_files(self, volumes, trash_dir_from_cli): - for path, volume in self.trash_directories.trash_directories_or_user( - volumes, trash_dir_from_cli): - for type, info_file in self.trash_directory.all_info_files(path): - if type == 'non_trashinfo': - self.logger.warning("Non .trashinfo file in info dir") - elif type == 'trashinfo': - try: - contents = self.contents_of(info_file) - original_location = parse_original_location(contents, - volume) - deletion_date = parse_deletion_date(contents) - backup_file_path = path_of_backup_copy(info_file) - trashedfile = TrashedFile(original_location, - deletion_date, - info_file, - backup_file_path) - yield trashedfile - except ValueError: - self.logger.warning("Non parsable trashinfo file: %s" % - info_file) - except IOError as e: - self.logger.warning(str(e)) - else: - self.logger.error("Unexpected file type: %s: %s", - type, info_file) - - -class TrashedFile: +class TrashedFile( + NamedTuple('TrashedFile', [ + ('original_location', str), + ('deletion_date', Optional[datetime.datetime]), + ('info_file', str), + ('original_file', str), + ])): """ Represent a trashed file. Each trashed file is persisted in two files: @@ -59,11 +25,9 @@ trash operation (instance of Path) """ - def __init__(self, original_location, - deletion_date, - info_file, - original_file): - self.original_location = original_location - self.deletion_date = deletion_date - self.info_file = info_file - self.original_file = original_file + def original_location_matches_path(self, path): + if path == os.path.sep: + return True + if self.original_location.startswith(path + os.path.sep): + return True + return self.original_location == path diff -Nru trash-cli-0.23.2.13.2/trashcli/restore/trashed_files.py trash-cli-0.23.11.10/trashcli/restore/trashed_files.py --- trash-cli-0.23.2.13.2/trashcli/restore/trashed_files.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/restore/trashed_files.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,97 @@ +from logging import Logger +from typing import Optional, Iterable, NamedTuple, Union + +from trashcli.lib.path_of_backup_copy import path_of_backup_copy +from trashcli.parse_trashinfo.parse_deletion_date import parse_deletion_date +from trashcli.parse_trashinfo.parse_original_location import \ + parse_original_location +from trashcli.restore.file_system import FileReader +from trashcli.restore.info_dir_searcher import InfoDirSearcher +from trashcli.restore.trashed_file import TrashedFile + + +class TrashedFiles: + def __init__(self, + logger, # type: Logger + file_reader, # type: FileReader + searcher, # type: InfoDirSearcher + ): + self.logger = logger + self.file_reader = file_reader + self.searcher = searcher + + def all_trashed_files(self, + trash_dir_from_cli, # type: Optional[str] + ): # type: (...) -> Iterable[TrashedFile] + for event in self.all_trashed_files_internal(trash_dir_from_cli): + if type(event) is NonTrashinfoFileFound: + self.logger.warning("Non .trashinfo file in info dir") + elif type(event) is NonParsableTrashInfo: + self.logger.warning( + "Non parsable trashinfo file: %s, because %s" % + event.path, event.reason) + elif type(event) is IOErrorReadingTrashInfo: + self.logger.warning(str(event)) + elif type(event) is TrashedFileFound: + yield event.trashed_file + else: + raise RuntimeError() + + def all_trashed_files_internal(self, + trash_dir_from_cli, # type: Optional[str] + ): # type: (...) -> Iterable[Event] + for info_file in self.searcher.all_file_in_info_dir(trash_dir_from_cli): + if info_file.type == 'non_trashinfo': + yield NonTrashinfoFileFound(info_file.path) + elif info_file.type == 'trashinfo': + try: + contents = self.file_reader.contents_of(info_file.path) + original_location = parse_original_location(contents, + info_file.volume) + deletion_date = parse_deletion_date(contents) + backup_file_path = path_of_backup_copy(info_file.path) + trashedfile = TrashedFile(original_location, + deletion_date, + info_file.path, + backup_file_path) + yield TrashedFileFound(trashedfile) + except ValueError as e: + yield NonParsableTrashInfo(info_file.path, e) + except IOError as e: + yield IOErrorReadingTrashInfo(info_file.path, str(e)) + else: + raise RuntimeError("Unexpected file type: %s: %s", + info_file.type, info_file.path) + + +class NonTrashinfoFileFound( + NamedTuple('NonTrashinfoFileFound', [ + ('path', str), + ])): pass + + +class TrashedFileFound( + NamedTuple('TrashedFileFound', [ + ('trashed_file', TrashedFile), + ])): pass + + +class NonParsableTrashInfo( + NamedTuple('NonParsableTrashInfo', [ + ('path', str), + ('reason', Exception), + ])): pass + + +class IOErrorReadingTrashInfo( + NamedTuple('IOErrorReadingTrashInfo', [ + ('path', str), + ('error', str), + ])): pass + + +Event = Union[ + NonTrashinfoFileFound, + TrashedFileFound, + NonParsableTrashInfo, + IOErrorReadingTrashInfo] diff -Nru trash-cli-0.23.2.13.2/trashcli/rm/cleanable_trashcan.py trash-cli-0.23.11.10/trashcli/rm/cleanable_trashcan.py --- trash-cli-0.23.2.13.2/trashcli/rm/cleanable_trashcan.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/rm/cleanable_trashcan.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,15 @@ +# Copyright (C) 2011-2021 Andrea Francia Bereguardo(PV) Italy +from trashcli.rm.file_remover import FileRemover +from trashcli.lib.path_of_backup_copy import path_of_backup_copy + + +class CleanableTrashcan: + def __init__(self, + file_remover, # type: FileRemover + ): + self._file_remover = file_remover + + def delete_trash_info_and_backup_copy(self, trash_info_path): + backup_copy = path_of_backup_copy(trash_info_path) + self._file_remover.remove_file_if_exists(backup_copy) + self._file_remover.remove_file2(trash_info_path) diff -Nru trash-cli-0.23.2.13.2/trashcli/rm/file_remover.py trash-cli-0.23.11.10/trashcli/rm/file_remover.py --- trash-cli-0.23.2.13.2/trashcli/rm/file_remover.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/rm/file_remover.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,6 @@ +from trashcli.fs import FsMethods + + +class FileRemover: + remove_file2 = FsMethods().remove_file2 + remove_file_if_exists = FsMethods().remove_file_if_exists diff -Nru trash-cli-0.23.2.13.2/trashcli/rm/filter.py trash-cli-0.23.11.10/trashcli/rm/filter.py --- trash-cli-0.23.2.13.2/trashcli/rm/filter.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/rm/filter.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,13 @@ +# Copyright (C) 2011-2021 Andrea Francia Bereguardo(PV) Italy +import fnmatch +import os + + +class Filter: + def __init__(self, pattern): + self.pattern = pattern + + def matches(self, original_location): + basename = os.path.basename(original_location) + subject = original_location if self.pattern[0] == '/' else basename + return fnmatch.fnmatchcase(subject, self.pattern) diff -Nru trash-cli-0.23.2.13.2/trashcli/rm/list_trashinfo.py trash-cli-0.23.11.10/trashcli/rm/list_trashinfo.py --- trash-cli-0.23.2.13.2/trashcli/rm/list_trashinfo.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/rm/list_trashinfo.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,44 @@ +# Copyright (C) 2011-2021 Andrea Francia Bereguardo(PV) Italy +import os +from abc import abstractmethod +from trashcli.compat import Protocol + +from trashcli.fs import ContentsOf +from trashcli.lib.dir_reader import DirReader +from trashcli.lib.trash_dir_reader import TrashDirReader +from trashcli.parse_trashinfo.parse_path import parse_path +from trashcli.parse_trashinfo.parser_error import ParseError + + +class FileContentReader(Protocol): + @abstractmethod + def contents_of(self, path): + raise NotImplementedError() + + +class ListTrashinfos: + def __init__(self, + file_content_reader, # type: ContentsOf + trash_dir_reader, # type: TrashDirReader + ): + self.trash_dir_reader = trash_dir_reader + self.file_content_reader = file_content_reader + + def list_from_volume_trashdir(self, trashdir_path, volume): + for trashinfo_path in self.trash_dir_reader.list_trashinfo( + trashdir_path): + trashinfo = self.file_content_reader.contents_of(trashinfo_path) + try: + path = parse_path(trashinfo) + except ParseError: + yield 'unable_to_parse_path', trashinfo_path + else: + complete_path = os.path.join(volume, path) + yield 'trashed_file', (complete_path, trashinfo_path) + + @staticmethod + def make(file_content_reader, # type: ContentsOf + dir_reader, # type: DirReader + ): + trash_dir_reader = TrashDirReader(dir_reader) + return ListTrashinfos(file_content_reader, trash_dir_reader) diff -Nru trash-cli-0.23.2.13.2/trashcli/rm/main.py trash-cli-0.23.11.10/trashcli/rm/main.py --- trash-cli-0.23.2.13.2/trashcli/rm/main.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/rm/main.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,31 @@ +# Copyright (C) 2011-2021 Andrea Francia Bereguardo(PV) Italy +import os +import sys + +from trashcli.fs import RealExists, RealIsStickyDir, RealIsSymLink, \ + RealContentsOf, RealEntriesIfDirExists +from trashcli.fstab.volume_listing import RealVolumesListing +from trashcli.rm.rm_cmd import RmCmd, RmFileSystemReader + + +def main(): + volumes_listing = RealVolumesListing() + cmd = RmCmd(environ=os.environ, + getuid=os.getuid, + volumes_listing=volumes_listing, + stderr=sys.stderr, + file_reader=RealRmFileSystemReader()) + + cmd.run(sys.argv, os.getuid()) + + return cmd.exit_code + + +class RealRmFileSystemReader(RmFileSystemReader, + RealExists, + RealIsStickyDir, + RealIsSymLink, + RealContentsOf, + RealEntriesIfDirExists, + ): + pass diff -Nru trash-cli-0.23.2.13.2/trashcli/rm/rm_cmd.py trash-cli-0.23.11.10/trashcli/rm/rm_cmd.py --- trash-cli-0.23.2.13.2/trashcli/rm/rm_cmd.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/rm/rm_cmd.py 2023-11-10 07:15:04.000000000 +0000 @@ -0,0 +1,81 @@ +# Copyright (C) 2011-2021 Andrea Francia Bereguardo(PV) Italy +from trashcli.compat import Protocol + +from trashcli.fs import ContentsOf +from trashcli.lib.dir_checker import DirChecker +from trashcli.lib.dir_reader import DirReader +from trashcli.lib.user_info import SingleUserInfoProvider +from trashcli.rm.cleanable_trashcan import CleanableTrashcan +from trashcli.rm.file_remover import FileRemover +from trashcli.rm.filter import Filter +from trashcli.rm.list_trashinfo import ListTrashinfos +from trashcli.trash_dirs_scanner import TrashDirsScanner, TopTrashDirRules, \ + trash_dir_found + + +class RmFileSystemReader(ContentsOf, + DirReader, + TopTrashDirRules.Reader, + Protocol): + pass + + +class RmCmd: + def __init__(self, + environ, + getuid, + volumes_listing, + stderr, + file_reader, # type: RmFileSystemReader + ): + self.environ = environ + self.getuid = getuid + self.volumes_listing = volumes_listing + self.stderr = stderr + self.file_reader = file_reader + + def run(self, argv, uid): + args = argv[1:] + self.exit_code = 0 + + if not args: + self.print_err('Usage:\n' + ' trash-rm PATTERN\n' + '\n' + 'Please specify PATTERN.\n' + 'trash-rm uses fnmatch.fnmatchcase to match patterns, see https://docs.python.org/3/library/fnmatch.html for more details.') + self.exit_code = 8 + return + + trashcan = CleanableTrashcan(FileRemover()) + cmd = Filter(args[0]) + + listing = ListTrashinfos.make(self.file_reader, self.file_reader) + + user_info_provider = SingleUserInfoProvider() + scanner = TrashDirsScanner(user_info_provider, + self.volumes_listing, + TopTrashDirRules(self.file_reader), + DirChecker()) + + for event, args in scanner.scan_trash_dirs(self.environ, uid): + if event == trash_dir_found: + path, volume = args + for type, arg in listing.list_from_volume_trashdir(path, + volume): + if type == 'unable_to_parse_path': + self.unable_to_parse_path(arg) + elif type == 'trashed_file': + original_location, info_file = arg + if cmd.matches(original_location): + trashcan.delete_trash_info_and_backup_copy( + info_file) + + def unable_to_parse_path(self, trashinfo): + self.report_error('{}: unable to parse \'Path\''.format(trashinfo)) + + def report_error(self, error_msg): + self.print_err('trash-rm: {}'.format(error_msg)) + + def print_err(self, msg): + self.stderr.write(msg + '\n') diff -Nru trash-cli-0.23.2.13.2/trashcli/rm.py trash-cli-0.23.11.10/trashcli/rm.py --- trash-cli-0.23.2.13.2/trashcli/rm.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/rm.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,131 +0,0 @@ -# Copyright (C) 2011-2021 Andrea Francia Bereguardo(PV) Italy -import fnmatch -import os -import sys - -from trashcli.fs import FileRemover, FileSystemReader -from trashcli.trash import ( - DirChecker, - ParseError, - TrashDirReader, - UserInfoProvider, - parse_path, - path_of_backup_copy, -) -from trashcli.trash_dirs_scanner import ( - TopTrashDirRules, - TrashDirsScanner, - trash_dir_found, -) - - -class CleanableTrashcan: - def __init__(self, file_remover): # type: (FileRemover) -> None - self._file_remover = file_remover - - def delete_trash_info_and_backup_copy(self, trash_info_path): - backup_copy = path_of_backup_copy(trash_info_path) - self._file_remover.remove_file_if_exists(backup_copy) - self._file_remover.remove_file(trash_info_path) - - -class RmCmd: - def __init__(self, - environ, - getuid, - volumes_listing, - stderr, - file_reader): - self.environ = environ - self.getuid = getuid - self.volumes_listing = volumes_listing - self.stderr = stderr - self.file_reader = file_reader - - def run(self, argv, uid): - args = argv[1:] - self.exit_code = 0 - - if not args: - self.print_err('Usage:\n' - ' trash-rm PATTERN\n' - '\n' - 'Please specify PATTERN.\n' - 'trash-rm uses fnmatch.fnmatchcase to match patterns, see https://docs.python.org/3/library/fnmatch.html for more details.') - self.exit_code = 8 - return - - trashcan = CleanableTrashcan(FileRemover()) - cmd = Filter(args[0]) - - listing = ListTrashinfos(self.file_reader) - - user_info_provider = UserInfoProvider() - scanner = TrashDirsScanner(user_info_provider, - self.volumes_listing, - TopTrashDirRules(self.file_reader), - DirChecker()) - - for event, args in scanner.scan_trash_dirs(self.environ, uid): - if event == trash_dir_found: - path, volume = args - for type, arg in listing.list_from_volume_trashdir(path, - volume): - if type == 'unable_to_parse_path': - self.unable_to_parse_path(arg) - elif type == 'trashed_file': - original_location, info_file = arg - if cmd.matches(original_location): - trashcan.delete_trash_info_and_backup_copy( - info_file) - - def unable_to_parse_path(self, trashinfo): - self.report_error('{}: unable to parse \'Path\''.format(trashinfo)) - - def report_error(self, error_msg): - self.print_err('trash-rm: {}'.format(error_msg)) - - def print_err(self, msg): - self.stderr.write(msg + '\n') - - -def main(): - from trashcli.list_mount_points import os_mount_points - from trashcli.fstab import VolumesListing - volumes_listing = VolumesListing(os_mount_points) - cmd = RmCmd(environ=os.environ - , getuid=os.getuid - , volumes_listing=volumes_listing - , stderr=sys.stderr - , file_reader=FileSystemReader()) - - cmd.run(sys.argv, os.getuid()) - - return cmd.exit_code - - -class Filter: - def __init__(self, pattern): - self.pattern = pattern - - def matches(self, original_location): - basename = os.path.basename(original_location) - subject = original_location if self.pattern[0] == '/' else basename - return fnmatch.fnmatchcase(subject, self.pattern) - - -class ListTrashinfos: - def __init__(self, file_reader): - self.file_reader = file_reader - - def list_from_volume_trashdir(self, trashdir_path, volume): - trashdir = TrashDirReader(self.file_reader) - for trashinfo_path in trashdir.list_trashinfo(trashdir_path): - trashinfo = self.file_reader.contents_of(trashinfo_path) - try: - path = parse_path(trashinfo) - except ParseError: - yield 'unable_to_parse_path', trashinfo_path - else: - complete_path = os.path.join(volume, path) - yield 'trashed_file', (complete_path, trashinfo_path) diff -Nru trash-cli-0.23.2.13.2/trashcli/shell_completion.py trash-cli-0.23.11.10/trashcli/shell_completion.py --- trash-cli-0.23.2.13.2/trashcli/shell_completion.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/shell_completion.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,9 +1,16 @@ +import argparse from copy import copy +from typing import Dict + try: - from shtab import add_argument_to, FILE, DIR + def convert_to_list(tuple): + return [item for item in tuple] + + + from shtab import add_argument_to, FILE, DIR # type: ignore - defaults = list(add_argument_to.__defaults__) + defaults = convert_to_list(add_argument_to.__defaults__) defaults[-1] = { "zsh": r""" # https://github.com/zsh-users/zsh/blob/19390a1ba8dc983b0a1379058e90cd51ce156815/Completion/Unix/Command/_rm#L72-L74 @@ -17,18 +24,28 @@ add_argument_to.__defaults__ = tuple(defaults) TRASH_FILES = copy(FILE) TRASH_DIRS = copy(DIR) + + + def complete_with(completion, # type: Dict[str, str] + action, # type: argparse.Action + ): + action.complete = completion # type: ignore + + + except ImportError: from argparse import Action TRASH_FILES = TRASH_DIRS = {} + class PrintCompletionAction(Action): def __call__(self, parser, namespace, values, option_string=None): print('Please install shtab firstly!') parser.exit(0) - def add_argument_to(parser, *args, **kwargs): + def add_argument_to(parser, *args, **kwargs): # type: ignore Action.complete = None # type: ignore parser.add_argument( '--print-completion', @@ -39,5 +56,10 @@ return parser + def complete_with(completion, # type: Dict[str, str] + action, # type: argparse.Action + ): + pass + TRASH_FILES.update({"zsh": "_trash_files"}) TRASH_DIRS.update({"zsh": "(${$(trash-list --trash-dirs)#parent_*:})"}) diff -Nru trash-cli-0.23.2.13.2/trashcli/super_enum.py trash-cli-0.23.11.10/trashcli/super_enum.py --- trash-cli-0.23.2.13.2/trashcli/super_enum.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/super_enum.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -class SuperEnum(object): - class __metaclass__(type): - def __iter__(self): - for item in self.__dict__: - if item == self.__dict__[item]: - yield item diff -Nru trash-cli-0.23.2.13.2/trashcli/trash.py trash-cli-0.23.11.10/trashcli/trash.py --- trash-cli-0.23.2.13.2/trashcli/trash.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/trash.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,245 +1,3 @@ # Copyright (C) 2007-2011 Andrea Francia Trivolzio(PV) Italy -from __future__ import absolute_import -import datetime -import logging -import os -import pwd - -version = '0.23.2.13.2' - -logger = logging.getLogger('trashcli.trash') -logger.setLevel(logging.WARNING) -logger.addHandler(logging.StreamHandler()) - -# Error codes (from os on *nix, hard coded for Windows): -EX_OK = getattr(os, 'EX_OK', 0) -EX_USAGE = getattr(os, 'EX_USAGE', 64) -EX_IOERR = getattr(os, 'EX_IOERR', 74) - - -def path_of_backup_copy(trashinfo_path): - trash_dir = os.path.dirname(os.path.dirname(trashinfo_path)) - basename = os.path.basename(trashinfo_path)[:-len('.trashinfo')] - return os.path.join(trash_dir, 'files', basename) - - -def home_trash_dir_path_from_env(environ): - if 'XDG_DATA_HOME' in environ: - return ['%(XDG_DATA_HOME)s/Trash' % environ] - elif 'HOME' in environ: - return ['%(HOME)s/.local/share/Trash' % environ] - return [] - - -def home_trash_dir_path_from_home(home_dir): - return '%s/.local/share/Trash' % home_dir - - -def home_trash_dir(environ, volume_of): - paths = home_trash_dir_path_from_env(environ) - for path in paths: - yield path, volume_of(path) - - -def volume_trash_dir1(volume, uid): - path = os.path.join(volume, '.Trash/%s' % uid) - yield path, volume - - -def volume_trash_dir2(volume, uid): - path = os.path.join(volume, ".Trash-%s" % uid) - yield path, volume - - -class UserInfo: - def __init__(self, home_trash_dir_paths, uid): - self.home_trash_dir_paths = home_trash_dir_paths - self.uid = uid - - -class UserInfoProvider: - @staticmethod - def get_user_info(environ, uid): - return [UserInfo(home_trash_dir_path_from_env(environ), uid)] - - -class AllUsersInfoProvider: - @staticmethod - def get_user_info(_environ, _uid): - for user in pwd.getpwall(): - yield UserInfo([home_trash_dir_path_from_home(user.pw_dir)], - user.pw_uid) - - -class DirChecker: - @staticmethod - def is_dir(path): - return os.path.isdir(path) - - -class HelpPrinter: - def __init__(self, out): - self.out = out - - def usage(self, usage): - self.println(usage) - self.println('') - - def summary(self, summary): - self.println(summary) - self.println('') - - def options(self, *line_describing_option): - self.println('Options:') - for line in line_describing_option: - self.println(line) - self.println('') - - def bug_reporting(self): - self.println( - "Report bugs to https://github.com/andreafrancia/trash-cli/issues") - - def println(self, line): - println(self.out, line) - - -def println(out, line): - out.write(line + '\n') - - -class PrintHelp: - def __init__(self, description, out): - self.description = description - self.printer = HelpPrinter(out) - - def my_print_help(self, program_name): - self.description(program_name, self.printer) - - -def print_version(out, program_name, version): - println(out, "%s %s" % (program_name, version)) - - -class DirReader: - def entries_if_dir_exists(self, path): # type: (str) -> list[str] - raise NotImplementedError() - - def exists(self, path): # type: (str) -> bool - raise NotImplementedError() - - -class TrashDirReader: - - def __init__(self, dir_reader): # type: (DirReader) -> None - self.dir_reader = dir_reader - - def list_orphans(self, path): - info_dir = os.path.join(path, 'info') - files_dir = os.path.join(path, 'files') - for entry in self.dir_reader.entries_if_dir_exists(files_dir): - trashinfo_path = os.path.join(info_dir, entry + '.trashinfo') - file_path = os.path.join(files_dir, entry) - if not self.dir_reader.exists(trashinfo_path): - yield file_path - - def list_trashinfo(self, path): - info_dir = os.path.join(path, 'info') - for entry in self.dir_reader.entries_if_dir_exists(info_dir): - if entry.endswith('.trashinfo'): - yield os.path.join(info_dir, entry) - - -class ParseError(ValueError): pass - - -def maybe_parse_deletion_date(contents): - result = Basket(unknown_date) - parser = ParseTrashInfo( - on_deletion_date=lambda date: result.collect(date), - on_invalid_date=lambda: result.collect(unknown_date) - ) - parser.parse_trashinfo(contents) - return result.collected - - -unknown_date = '????-??-?? ??:??:??' - -try: - from urllib import unquote -except ImportError: - from urllib.parse import unquote - - -def do_nothing(*argv, **argvk): pass - - -class ParseTrashInfo: - def __init__(self, - on_deletion_date=do_nothing, - on_invalid_date=do_nothing, - on_path=do_nothing): - self.found_deletion_date = on_deletion_date - self.found_invalid_date = on_invalid_date - self.found_path = on_path - - def parse_trashinfo(self, contents): - found_deletion_date = False - for line in contents.split('\n'): - if not found_deletion_date and line.startswith('DeletionDate='): - found_deletion_date = True - try: - date = datetime.datetime.strptime( - line, "DeletionDate=%Y-%m-%dT%H:%M:%S") - except ValueError: - self.found_invalid_date() - else: - self.found_deletion_date(date) - - if line.startswith('Path='): - path = unquote(line[len('Path='):]) - self.found_path(path) - - -class Basket: - def __init__(self, initial_value=None): - self.collected = initial_value - - def collect(self, value): - self.collected = value - - -def parse_deletion_date(contents): - result = Basket() - parser = ParseTrashInfo(on_deletion_date=result.collect) - parser.parse_trashinfo(contents) - return result.collected - - -def parse_path(contents): - for line in contents.split('\n'): - if line.startswith('Path='): - return unquote(line[len('Path='):]) - raise ParseError('Unable to parse Path') - - -def parse_original_location(contents, volume_path): - path = parse_path(contents) - return os.path.join(volume_path, path) - - -class Clock: - def __init__(self, real_now, errors): - self.real_now = real_now - self.errors = errors - - def get_now_value(self, environ): - if 'TRASH_DATE' in environ: - try: - return datetime.datetime.strptime(environ['TRASH_DATE'], - "%Y-%m-%dT%H:%M:%S") - except ValueError: - self.errors.print_error('invalid TRASH_DATE: %s' % - environ['TRASH_DATE']) - return self.real_now() - return self.real_now() +version = '0.23.11.10' diff -Nru trash-cli-0.23.2.13.2/trashcli/trash_dirs_scanner.py trash-cli-0.23.11.10/trashcli/trash_dirs_scanner.py --- trash-cli-0.23.2.13.2/trashcli/trash_dirs_scanner.py 2023-02-13 07:49:38.000000000 +0000 +++ trash-cli-0.23.11.10/trashcli/trash_dirs_scanner.py 2023-11-10 07:15:04.000000000 +0000 @@ -1,6 +1,11 @@ import os +from typing import Iterable +from trashcli.compat import Protocol -from trashcli.fstab import VolumesListing +from trashcli.fs import PathExists, IsStickyDir, IsSymLink +from trashcli.fstab.volume_listing import VolumesListing +from trashcli.lib.dir_checker import DirChecker +from trashcli.lib.user_info import UserInfoProvider class MyEnum(str): @@ -39,15 +44,8 @@ class TopTrashDirRules: - class Reader: - def exists(self, path): # type: (str) -> bool - raise NotImplementedError() - - def is_sticky_dir(self, path): # type: (str) -> bool - raise NotImplementedError() - - def is_symlink(self, path): # type: (str) -> bool - raise NotImplementedError() + class Reader(PathExists, IsStickyDir, IsSymLink, Protocol): + pass def __init__(self, reader): # type: (Reader) -> None self.reader = reader @@ -66,10 +64,11 @@ class TrashDirsScanner: def __init__(self, - user_info_provider, - volumes_listing, - top_trash_dir_rules, - dir_checker): + user_info_provider, # type: UserInfoProvider + volumes_listing, # type: VolumesListing + top_trash_dir_rules, # type: TopTrashDirRules + dir_checker, # type: DirChecker + ): self.user_info_provider = user_info_provider self.volumes_listing = volumes_listing # type: VolumesListing self.top_trash_dir_rules = top_trash_dir_rules @@ -98,7 +97,8 @@ yield trash_dir_found, TrashDir(alt_top_trash_dir, volume) -def only_found(events): +def only_found(events, # type: Iterable[TrashDir] + ): # type: (...) -> Iterable[TrashDir] for event, args in events: if event == trash_dir_found: yield args