Improve testability

This commit is contained in:
Gabriel Augendre 2019-12-28 13:31:34 +01:00
parent 64068989a1
commit 8787129be6
No known key found for this signature in database
GPG key ID: 1E693F4CE4AEE7B4
6 changed files with 142 additions and 113 deletions

64
cleantoots/clean.py Normal file
View file

@ -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")

View file

@ -1,19 +1,25 @@
import configparser
import os import os
import sys import sys
import click import click
from mastodon import Mastodon 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 @click.pass_obj
def config(config): def config_command(config):
"""Manage cleantoot's config.""" """Manage cleantoot's config."""
pass pass
@config.command() @config_command.command()
@click.pass_obj @click.pass_obj
def setup(config): def setup(config):
"""Initial setup for configuration directories and files.""" """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)) click.echo("You may want to edit the file. Use: {}.".format(command))
return return
default_config = configparser.ConfigParser() default_config = _get_default_config()
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",
}
with open(config.main_file, "w") as _file: with open(config.main_file, "w") as _file:
default_config.write(_file) default_config.write(_file)
click.secho("{} written.".format(config.main_file), fg="green") 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 base URL of your Mastodon instance")
click.echo("* The toots you want to protect") click.echo("* The toots you want to protect")
if sys.stdout.isatty() and sys.stdin.isatty(): if _is_tty():
click.echo() click.echo()
click.secho("We're going to open the file for you now.") click.secho("We're going to open the file for you now.")
click.pause() click.pause()
click.edit(filename=config.main_file) click.edit(filename=config.main_file)
@config.command(name="list") @config_command.command(name="list")
@click.pass_obj @click.pass_obj
def list_(config): def list_(config):
"""Display parsed config.""" """Display parsed config."""
if not config.sections(): if not _config_has_sections(config):
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 return
for section_name in config.sections(): for section_name in config.sections():
click.secho(section_name, bold=True) click.secho(section_name, bold=True)
@ -73,14 +64,11 @@ def list_(config):
click.echo() click.echo()
@config.command() @config_command.command()
@click.pass_obj @click.pass_obj
def edit(config): def edit(config):
"""Edit config file.""" """Edit config file."""
if not config.sections(): if not _config_has_sections(config):
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 return
if sys.stdout.isatty() and sys.stdin.isatty(): if sys.stdout.isatty() and sys.stdin.isatty():
click.edit(filename=config.main_file) 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") click.secho("Not running in a terminal, can't open file.", fg="red")
@config.command() @config_command.command()
@click.pass_obj @click.pass_obj
def login(config): def login(config):
"""Fetch credentials for each app described in config file.""" """Fetch credentials for each app described in config file."""
if not config.sections(): if not _config_has_sections(config):
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 return
for section in config.sections(): for section in config.sections():
section = config[section] section = config[section]
@ -105,21 +90,6 @@ def login(config):
to_file=config.file(section.get("app_secret_file")), to_file=config.file(section.get("app_secret_file")),
) )
mastodon = Mastodon(client_id=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(): _open_url(mastodon.auth_request_url())
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()
)
)
code = click.prompt("Enter code for {}".format(section.get("api_base_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"))) mastodon.log_in(code=code, to_file=config.file(section.get("user_secret_file")))

View file

@ -3,10 +3,9 @@ import os
import pathlib import pathlib
import click import click
import pendulum
from mastodon import Mastodon
from cleantoots import config as config_commands from cleantoots import config as config_commands
from cleantoots import clean as clean_commands
HOME = pathlib.Path.home() HOME = pathlib.Path.home()
DEFAULT_CONFIG_DIR = click.get_app_dir("cleantoots") 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) ctx.obj = CleanTootsConfig(config_dir, config_file)
cli.add_command(config_commands.config) cli.add_command(config_commands.config_command)
cli.add_command(clean_commands.clean)
@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")
if __name__ == "__main__": if __name__ == "__main__":
cli() cli()

49
cleantoots/utils.py Normal file
View file

@ -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

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "cleantoots" name = "cleantoots"
version = "0.3.0" version = "0.3.1"
description = "Cleanup your toot history." description = "Cleanup your toot history."
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
authors = ["Gabriel Augendre <gabriel@augendre.info>"] authors = ["Gabriel Augendre <gabriel@augendre.info>"]

View file

@ -13,7 +13,7 @@ class SetupConfigTestCase(unittest.TestCase):
def test_setup_config(self): def test_setup_config(self):
with self.runner.isolated_filesystem(): with self.runner.isolated_filesystem():
result = self.runner.invoke(cli, ["-d", ".", "config", "setup"]) 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.assertIn("config.ini written", result.output)
self.assertTrue(os.path.isfile(os.path.join(".", "config.ini"))) 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.runner.invoke(cli, ["-d", ".", "config", "setup"])
self.assertTrue(os.path.isfile(os.path.join(".", "config.ini"))) self.assertTrue(os.path.isfile(os.path.join(".", "config.ini")))
result = self.runner.invoke(cli, ["-d", ".", "config", "setup"]) 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("Not touching anything", result.output)
self.assertIn("cleantoots config edit", result.output) self.assertIn("cleantoots config edit", result.output)
@ -64,6 +64,10 @@ class SetupConfigTestCase(unittest.TestCase):
) )
self.assertIn("Enter code for", result.output) 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__": if __name__ == "__main__":
unittest.main() unittest.main()