From 8787129be674c360ee730df6b7ce2ca3c18eb463 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sat, 28 Dec 2019 13:31:34 +0100 Subject: [PATCH] Improve testability --- cleantoots/clean.py | 64 +++++++++++++++++++++++++++++++++++++++++ cleantoots/config.py | 68 +++++++++++++------------------------------- cleantoots/main.py | 64 ++--------------------------------------- cleantoots/utils.py | 49 +++++++++++++++++++++++++++++++ pyproject.toml | 2 +- tests/test_cli.py | 8 ++++-- 6 files changed, 142 insertions(+), 113 deletions(-) create mode 100644 cleantoots/clean.py create mode 100644 cleantoots/utils.py diff --git a/cleantoots/clean.py b/cleantoots/clean.py new file mode 100644 index 0000000..87dd5fb --- /dev/null +++ b/cleantoots/clean.py @@ -0,0 +1,64 @@ +import click +import pendulum +from mastodon import Mastodon + +from cleantoots.utils import _config_has_sections + + +@click.command() +@click.option( + "--delete", + help="Delete toots that match the rules without confirmation. This is a destructive operation. " + "Without this flags, toots will only be listed.", + is_flag=True, +) +@click.pass_obj +def clean(config, delete): + """ + Delete Toots based on rules in config file. + + Without the `--delete` flag, toots will only be displayed. + """ + if not _config_has_sections(config): + return + for section in config.sections(): + section = config[section] + user_secret_file = config.file(section.get("user_secret_file")) + mastodon = Mastodon(access_token=user_secret_file) + user = mastodon.me() + page = mastodon.account_statuses(user["id"]) + would_delete = [] + while page: + for toot in page: + if ( + toot["reblogs_count"] >= section.getint("boost_limit") + or toot["favourites_count"] >= section.getint("favorite_limit") + or toot["id"] + in map(int, section.get("protected_toots", "").split()) + or toot["created_at"] + >= pendulum.now(tz=section.get("timezone")).subtract( + days=section.getint("days_count") + ) + ): + continue + would_delete.append(toot) + + page = mastodon.fetch_next(page) + + if not delete: + if not would_delete: + click.secho("No toot would be deleted given the rules.", fg="blue") + return + click.secho( + "Would delete {count} toots:".format(count=len(would_delete)), fg="blue" + ) + for toot in would_delete: + click.echo(toot["id"]) + click.echo(toot["content"]) + click.echo() + else: + click.echo("Deleting toots...") + with click.progressbar(would_delete) as bar: + for toot in bar: + mastodon.status_delete(toot) + click.secho("Deleted toot {}".format(toot["id"]), fg="green") diff --git a/cleantoots/config.py b/cleantoots/config.py index 245259d..8384bb4 100644 --- a/cleantoots/config.py +++ b/cleantoots/config.py @@ -1,19 +1,25 @@ -import configparser import os import sys import click from mastodon import Mastodon +from cleantoots.utils import ( + _config_has_sections, + _open_url, + _get_default_config, + _is_tty, +) -@click.group() + +@click.group("config") @click.pass_obj -def config(config): +def config_command(config): """Manage cleantoot's config.""" pass -@config.command() +@config_command.command() @click.pass_obj def setup(config): """Initial setup for configuration directories and files.""" @@ -26,19 +32,7 @@ def setup(config): click.echo("You may want to edit the file. Use: {}.".format(command)) return - default_config = configparser.ConfigParser() - default_config["DEFAULT"] = { - "boost_limit": 5, - "favorite_limit": 5, - "days_count": 30, - "timezone": "Europe/Paris", - } - default_config["Mastodon.social"] = { - "api_base_url": "https://mastodon.social", - "app_secret_file": "mastodon_social_app.secret", - "user_secret_file": "mastodon_social_user.secret", - "protected_toots": "1234\n5678", - } + default_config = _get_default_config() with open(config.main_file, "w") as _file: default_config.write(_file) click.secho("{} written.".format(config.main_file), fg="green") @@ -49,21 +43,18 @@ def setup(config): ) click.echo("* The base URL of your Mastodon instance") click.echo("* The toots you want to protect") - if sys.stdout.isatty() and sys.stdin.isatty(): + if _is_tty(): click.echo() click.secho("We're going to open the file for you now.") click.pause() click.edit(filename=config.main_file) -@config.command(name="list") +@config_command.command(name="list") @click.pass_obj def list_(config): """Display parsed config.""" - if not config.sections(): - click.secho("The config file doesn't seem to have any section.", fg="yellow") - command = click.style("cleantoots config setup", bold=True) - click.secho("You should set it up first. Use: {}".format(command)) + if not _config_has_sections(config): return for section_name in config.sections(): click.secho(section_name, bold=True) @@ -73,14 +64,11 @@ def list_(config): click.echo() -@config.command() +@config_command.command() @click.pass_obj def edit(config): """Edit config file.""" - if not config.sections(): - click.secho("The config file doesn't seem to have any section.", fg="yellow") - command = click.style("cleantoots config setup", bold=True) - click.secho("You should set it up first. Use: {}".format(command)) + if not _config_has_sections(config): return if sys.stdout.isatty() and sys.stdin.isatty(): click.edit(filename=config.main_file) @@ -88,14 +76,11 @@ def edit(config): click.secho("Not running in a terminal, can't open file.", fg="red") -@config.command() +@config_command.command() @click.pass_obj def login(config): """Fetch credentials for each app described in config file.""" - if not config.sections(): - click.secho("The config file doesn't seem to have any section.", fg="yellow") - command = click.style("cleantoots config setup", bold=True) - click.secho("You should set it up first. Use: {}".format(command)) + if not _config_has_sections(config): return for section in config.sections(): section = config[section] @@ -105,21 +90,6 @@ def login(config): to_file=config.file(section.get("app_secret_file")), ) mastodon = Mastodon(client_id=config.file(section.get("app_secret_file"))) - if sys.stdout.isatty() and sys.stdin.isatty(): - click.echo( - "We will now open a browser for each account set in the config file." - ) - click.echo( - "You'll need to authenticate and then copy the code provided in the web " - "page back into this terminal, upon prompt." - ) - click.pause() - click.launch(mastodon.auth_request_url()) - else: - click.echo( - "Go to {}, authenticate and enter the code below.".format( - mastodon.auth_request_url() - ) - ) + _open_url(mastodon.auth_request_url()) code = click.prompt("Enter code for {}".format(section.get("api_base_url"))) mastodon.log_in(code=code, to_file=config.file(section.get("user_secret_file"))) diff --git a/cleantoots/main.py b/cleantoots/main.py index 5623e9c..74959b7 100644 --- a/cleantoots/main.py +++ b/cleantoots/main.py @@ -3,10 +3,9 @@ import os import pathlib import click -import pendulum -from mastodon import Mastodon from cleantoots import config as config_commands +from cleantoots import clean as clean_commands HOME = pathlib.Path.home() DEFAULT_CONFIG_DIR = click.get_app_dir("cleantoots") @@ -58,65 +57,8 @@ def cli(ctx, config_dir, config_file): ctx.obj = CleanTootsConfig(config_dir, config_file) -cli.add_command(config_commands.config) - - -@cli.command() -@click.option( - "--delete", - help="Delete toots that match the rules without confirmation. This is a destructive operation. " - "Without this flags, toots will only be listed.", - is_flag=True, -) -@click.pass_obj -def clean(delete, config): - """ - Delete Toots based on rules in config file. - - Without the `--delete` flag, toots will only be displayed. - """ - for section in config.sections(): - section = config[section] - user_secret_file = config.file(section.get("user_secret_file")) - mastodon = Mastodon(access_token=user_secret_file) - user = mastodon.me() - page = mastodon.account_statuses(user["id"]) - would_delete = [] - while page: - for toot in page: - if ( - toot["reblogs_count"] >= section.getint("boost_limit") - or toot["favourites_count"] >= section.getint("favorite_limit") - or toot["id"] - in map(int, section.get("protected_toots", "").split()) - or toot["created_at"] - >= pendulum.now(tz=section.get("timezone")).subtract( - days=section.getint("days_count") - ) - ): - continue - would_delete.append(toot) - - page = mastodon.fetch_next(page) - - if not delete: - if not would_delete: - click.secho("No toot would be deleted given the rules.", fg="blue") - return - click.secho( - "Would delete {count} toots:".format(count=len(would_delete)), fg="blue" - ) - for toot in would_delete: - click.echo(toot["id"]) - click.echo(toot["content"]) - click.echo() - else: - click.echo("Deleting toots...") - with click.progressbar(would_delete) as bar: - for toot in bar: - mastodon.status_delete(toot) - click.secho("Deleted toot {}".format(toot["id"]), fg="green") - +cli.add_command(config_commands.config_command) +cli.add_command(clean_commands.clean) if __name__ == "__main__": cli() diff --git a/cleantoots/utils.py b/cleantoots/utils.py new file mode 100644 index 0000000..275aa02 --- /dev/null +++ b/cleantoots/utils.py @@ -0,0 +1,49 @@ +import configparser +import sys + +import click + + +def _is_tty(): + return sys.stdout.isatty() and sys.stdin.isatty() + + +def _config_has_sections(config): + if not config.sections(): + click.secho("The config file doesn't seem to have any section.", fg="yellow") + command = click.style("cleantoots config setup", bold=True) + click.secho("You should set it up first. Use: {}".format(command)) + return False + return True + + +def _open_url(url): + if _is_tty(): + click.echo( + "We will now open a browser for each account set in the config file." + ) + click.echo( + "You'll need to authenticate and then copy the code provided in the web " + "page back into this terminal, upon prompt." + ) + click.pause() + click.launch(url) + else: + click.echo("Go to {}, authenticate and enter the code below.".format(url)) + + +def _get_default_config(): + default_config = configparser.ConfigParser() + default_config["DEFAULT"] = { + "boost_limit": 5, + "favorite_limit": 5, + "days_count": 30, + "timezone": "Europe/Paris", + } + default_config["Mastodon.social"] = { + "api_base_url": "https://mastodon.social", + "app_secret_file": "mastodon_social_app.secret", + "user_secret_file": "mastodon_social_user.secret", + "protected_toots": "1234\n5678", + } + return default_config diff --git a/pyproject.toml b/pyproject.toml index 693d370..05d74eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cleantoots" -version = "0.3.0" +version = "0.3.1" description = "Cleanup your toot history." license = "GPL-3.0-or-later" authors = ["Gabriel Augendre "] diff --git a/tests/test_cli.py b/tests/test_cli.py index e09c338..fbcd91a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -13,7 +13,7 @@ class SetupConfigTestCase(unittest.TestCase): def test_setup_config(self): with self.runner.isolated_filesystem(): result = self.runner.invoke(cli, ["-d", ".", "config", "setup"]) - self.assertEqual(result.exit_code, 0) + self.assertEqual(0, result.exit_code) self.assertIn("config.ini written", result.output) self.assertTrue(os.path.isfile(os.path.join(".", "config.ini"))) @@ -22,7 +22,7 @@ class SetupConfigTestCase(unittest.TestCase): self.runner.invoke(cli, ["-d", ".", "config", "setup"]) self.assertTrue(os.path.isfile(os.path.join(".", "config.ini"))) result = self.runner.invoke(cli, ["-d", ".", "config", "setup"]) - self.assertEqual(result.exit_code, 0) + self.assertEqual(0, result.exit_code) self.assertIn("Not touching anything", result.output) self.assertIn("cleantoots config edit", result.output) @@ -64,6 +64,10 @@ class SetupConfigTestCase(unittest.TestCase): ) self.assertIn("Enter code for", result.output) + def test_clean_exists(self): + result = self.runner.invoke(cli, ["-d", ".", "clean"]) + self.assertEqual(0, result.exit_code) + if __name__ == "__main__": unittest.main()