diff --git a/toot/commands.py b/toot/commands.py index 3fee651..202a3d5 100644 --- a/toot/commands.py +++ b/toot/commands.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- +import sys + from toot import api, config from toot.auth import login_interactive, login_browser_interactive, create_app_interactive from toot.exceptions import ConsoleError, NotFoundError from toot.output import (print_out, print_instance, print_account, print_search_results, print_timeline, print_notifications) -from toot.utils import assert_domain_exists, multiline_input, EOF_KEY +from toot.utils import assert_domain_exists, editor_input, multiline_input, EOF_KEY def get_timeline_generator(app, user, args): @@ -76,9 +78,17 @@ def curses(app, user, args): def post(app, user, args): + # TODO: this might be achievable, explore options + if args.editor and not sys.stdin.isatty(): + raise ConsoleError("Cannot run editor if not in tty.") + if args.media and len(args.media) > 4: raise ConsoleError("Cannot attach more than 4 files.") + # Read any text that might be piped to stdin + if not args.text and not sys.stdin.isatty(): + args.text = sys.stdin.read().rstrip() + if args.media: media = [_do_upload(app, user, file) for file in args.media] media_ids = [m["id"] for m in media] @@ -89,7 +99,9 @@ def post(app, user, args): if media and not args.text: args.text = "\n".join(m['text_url'] for m in media) - if not args.text: + if args.editor: + args.text = editor_input(args.editor, args.text) + elif not args.text: print_out("Write or paste your toot. Press {} to post it.".format(EOF_KEY)) args.text = multiline_input() diff --git a/toot/console.py b/toot/console.py index 82c33a0..e5ea144 100644 --- a/toot/console.py +++ b/toot/console.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -import os -import sys import logging +import os +import shutil +import sys from argparse import ArgumentParser, FileType, ArgumentTypeError from collections import namedtuple @@ -39,6 +40,21 @@ def timeline_count(value): return n +def editor(value): + if not value: + raise ArgumentTypeError( + "Editor not specified in --editor option and $EDITOR environment " + "variable not set." + ) + + # Check editor executable exists + exe = shutil.which(value) + if not exe: + raise ArgumentTypeError(f"Editor `{value}` not found") + + return exe + + Command = namedtuple("Command", ["name", "description", "require_auth", "arguments"]) @@ -298,6 +314,13 @@ POST_COMMANDS = [ "type": language, "help": "ISO 639-2 language code of the toot, to skip automatic detection", }), + (["-e", "--editor"], { + "type": editor, + "nargs": "?", + "const": os.getenv("EDITOR", ""), # option given without value + "help": "Specify an editor to compose your toot, " + "defaults to editor defined in $EDITOR env variable.", + }), ], require_auth=True, ), @@ -500,12 +523,6 @@ def main(): filename = os.getenv("TOOT_LOG_FILE") logging.basicConfig(level=logging.DEBUG, filename=filename) - # If something is piped in, append it to commandline arguments - if not sys.stdin.isatty(): - stdin = sys.stdin.read() - if stdin: - sys.argv.append(stdin) - command_name = sys.argv[1] if len(sys.argv) > 1 else None args = sys.argv[2:] @@ -519,5 +536,5 @@ def main(): except (ConsoleError, ApiError) as e: print_err(str(e)) sys.exit(1) - except KeyboardInterrupt as e: + except KeyboardInterrupt: pass diff --git a/toot/utils.py b/toot/utils.py index aa3a176..5d26d6e 100644 --- a/toot/utils.py +++ b/toot/utils.py @@ -3,6 +3,8 @@ import os import re import socket +import subprocess +import tempfile import unicodedata import warnings @@ -88,3 +90,27 @@ def multiline_input(): break return "\n".join(lines).strip() + + +EDITOR_INPUT_INSTRUCTIONS = """ +# Please enter your toot. Lines starting with '#' will be ignored, and an empty +# message aborts the post. +""" + + +def editor_input(editor, initial_text): + """Lets user input text using an editor.""" + initial_text = (initial_text or "") + EDITOR_INPUT_INSTRUCTIONS + + with tempfile.NamedTemporaryFile() as f: + f.write(initial_text.encode()) + f.flush() + + subprocess.run([editor, f.name]) + + f.seek(0) + text = f.read().decode() + + lines = text.strip().splitlines() + lines = (l for l in lines if not l.startswith("#")) + return "\n".join(lines)