#!/usr/bin/env python3 import difflib import functools from typing import Callable import click from clintermission import cli_select_item # type: ignore import editor # type: ignore from sievelib.managesieve import Client # type: ignore def default_options(f: Callable) -> Callable: @click.option('--server', prompt=True, help='sieve server to connect to') @click.option('-u', '--user', prompt=True, help='user to connect as') @click.option('--password', prompt=True, hide_input=True, help='Passwort to connect with. It\'s discouraged to pass passwords.' 'Use the automatic prompt instead.') @click.option('--starttls/--no-starttls', default=True, help='Connect with starttls') @functools.wraps(f) def wrapper(*args, **kwargs): f(*args, **kwargs) return wrapper @click.group() def main(): pass @main.command() @default_options @click.option('--script-name', help='script name to set as active. If not provided, choices will be presented.') def active(server: str, user: str, password: str, starttls: bool, script_name: str) -> int: """Connect to a SIEVE server and set a script as active""" c = Client(server) c.connect(user, password, starttls=starttls) active, scripts = c.listscripts() if not script_name: if len(scripts + [active]) == 1: click.echo('Only a single script available: {}'.format((scripts + [active])[0])) _, script_name = cli_select_item(zip(sorted(['{} (active)'.format(active)] + scripts), sorted([active] + scripts))) if active == script_name: click.echo('Script {} is already active.'.format(script_name)) return 0 if script_name not in scripts: click.echo('Error: script {} cannot be found. Valid choices are: {}' .format(script_name, ', '.join(scripts))) return 1 c.setactive(script_name) return 0 DEFAULT_TEMPLATE = """\ # Example script. Edit as you like require ["fileinto"]; # rule:[amazon] if header :contains "To" "amazon@" { fileinto "INBOX.amazon"; stop; } # rule:[games] if anyof (header :contains "To" "gw2@", header :contains "To" "humblebundle@", header :contains "To" "steam@", header :contains "To" "socialclub@", header :contains "To" "uplay@" ) { fileinto "INBOX.games"; stop; } # rule:[paypal] if header :contains "To" "paypal@" { fileinto "INBOX.paypal"; stop; } """ @main.command() @default_options @click.option('--script-name', help='Edit this script or create it') def edit(server: str, user: str, password: str, starttls: bool, script_name: str) -> None: """Connect to a SIEVE server, download, edit and upload a script""" c = Client(server) c.connect(user, password, starttls=starttls) active, scripts = c.listscripts() if not script_name: _, script_name = cli_select_item(zip(sorted(['{} (active)'.format(active)] + scripts), sorted([active] + scripts))) if script_name not in (scripts + [active]): msg = 'Script {} does not exist. Create new script?' click.confirm(msg.format(script_name), abort=True) original_content = DEFAULT_TEMPLATE newly_created = True else: original_content = c.getscript(script_name) newly_created = False new_content = editor.edit(contents=original_content).decode('utf-8') if new_content.strip() == original_content and not newly_created: click.echo('No changes. Aborting.') return while not c.checkscript(new_content): click.confirm('Script is invalid. Try again?', abort=True) new_content = editor.edit(contents=new_content).decode('utf-8') if new_content == original_content and not newly_created: click.echo('No changes. Aborting.') return diff = difflib.unified_diff(original_content.splitlines(), new_content.splitlines(), fromfile='before', tofile='after') if not newly_created: click.echo('Following changes were made:\n{}'.format('\n'.join(diff))) click.confirm('Upload new version?', abort=True) c.putscript(script_name, new_content) if __name__ == '__main__': main()