blob: f257a20e0fa1b427eb3fb3a5bcaa38eb3b79cd86 [file]
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import os
import subprocess
import tempfile
import _hkml_cli
import _hkml_list_cache
import _hkml_subproc
import hkml_open
import hkml_send
import hkml_signature
SIGNATURE_WARNING = ['',
'/*',
' * !THE FOLLOWING COMMENT WAS AUTOMATICALLY ADDED BY HKML',
' * below are signatures added by "hkml".',
' * edit signatures below, or use "hkml signature".',
' * If you leave this block untouched, then hkml will',
' * automatically ignore the block before sending.',
' */',]
SIGNATURE_WARNING_LEN = len(SIGNATURE_WARNING) - 1 # first line is blank
coloring_notice_header = [
'/*',
' * !THE FOLLOWING COMMENT WAS AUTOMATICALLY ADDED BY HKML.',
' * If you leave this block untouched, then hkml will automatically',
' * remove this block before sending.',
' *',
]
coloring_notice_footer = [' */']
coloring_notice_vim = coloring_notice_header + [
' * Note that Original and past lines of the draft are colored',
' * using vim commands. You can disable the coloring using',
' * ":match past" and ":2match orig" with random keywords.',
' * E.g., ":match past 32l4kg3l" and "2match orig sdflse334s".',
] + coloring_notice_footer
coloring_notice_kak = coloring_notice_header + [
' * Note that Original and past lines of the draft are colored',
' * using kakoune commands. You can disable the coloring using',
' * ":remove-highlighter window/past" and',
' * ":remove-highlighter window/orig".',
] + coloring_notice_footer
coloring_notices = [coloring_notice_vim, coloring_notice_kak]
def git_sendemail_valid_recipients(recipients):
"""each line should be less than 998 char"""
if not recipients:
return ''
# TODO: Could name contain ','?
if len(recipients) < 998:
return recipients
addresses = recipients.split(',')
lines = []
line = ''
for addr in addresses[1:]:
if len(line) + len(addr) + len(', ') > 998:
lines.append(line)
line = '\t'
line += '%s,' % addr
lines.append(line)
lines[-1] = lines[-1][:-1]
return '\n'.join(lines)
def get_git_config(config_name):
try:
result = subprocess.check_output(
['git', 'config', config_name]).decode().strip()
return result, None
except Exception as e:
return None, '"git config %s" failed (%s)' % (config_name, e)
def format_mbox(subject, in_reply_to, to, cc, body, from_, draft_mail,
attach_files=None):
if draft_mail is not None:
return hkml_open.mail_display_str(draft_mail, head_columns=None,
valid_mbox=False,
for_draft_continue=True)
lines = []
if not subject:
subject = '/* write subject here */'
if not to:
to = ['/* write recipients here and REMOVE this comment */']
if not cc:
cc = ['/* write cc recipients here and REMOVE this comment */']
if from_ is None:
from_, err = get_git_config('sendemail.from')
if err is not None:
name, err = get_git_config('user.name')
email, err = get_git_config('user.email')
if email is None:
from_ = '/* fill up please */'
else:
from_ = '%s <%s>' % (name, email)
lines.append('Subject: %s' % subject)
lines.append('From: %s' % from_)
if in_reply_to:
lines.append('In-Reply-To: %s' % in_reply_to)
for addr in to:
addr = git_sendemail_valid_recipients(addr)
lines.append('To: %s' % addr)
for addr in cc:
addr = git_sendemail_valid_recipients(addr)
lines.append('Cc: %s' % addr)
lines.append('')
if not body:
body = '/* write your message here (keep the above blank line) */'
lines.append(body)
signatures = hkml_signature.read_signatures_file()
if len(signatures) > 0:
lines += SIGNATURE_WARNING
for signature in signatures:
lines.append('')
lines.append(signature)
if attach_files is not None:
for idx, attach_file in enumerate(attach_files):
marker_line = '==== Attachment %d (%s) ====' % (idx, attach_file)
with open(attach_file, 'r') as f:
lines.append('\n%s\n%s' % (marker_line, f.read()))
return '\n'.join(lines)
def ask_editor(default_editor):
if default_editor is not None:
choices = [default_editor]
else:
choices = []
for editor in ['vim', 'nvim', 'emacs', 'nano', 'kak']:
if not editor in choices:
if _hkml_subproc.cmd_available(editor):
choices.append(editor)
if choices == []:
print('please install vim, nvim, emacs, nano or kak and retry')
exit(1)
print('I will open a text editor to let you edit the mail.')
print('What text editor shall I use?')
print()
for idx, cmd in enumerate(choices):
if idx == 0:
print('%d: %s # DEFAULT' % (idx + 1, cmd))
else:
print('%d: %s' % (idx + 1, cmd))
print()
answer = input('Enter the number, please (%s by default): ' % choices[0])
try:
cmd = choices[int(answer) - 1]
except:
cmd = choices[0]
return cmd
def add_coloring_explanation(file_path, notice):
with open(file_path, 'r') as f:
content = f.read()
header = content.split('\n\n')[0]
body_start_line = len(header.splitlines()) + 1
lines = content.splitlines()
new_lines = lines[:body_start_line] + notice
new_lines += lines[body_start_line:]
with open(file_path, 'w') as f:
f.write('\n'.join(new_lines))
def open_editor(file_path, target_desc='mail', cursor_row=0, is_reply=False):
editor = os.environ.get('EDITOR')
editor = ask_editor(editor)
cmd = [editor, file_path]
if cursor_row != 0 and editor in ['vim', 'nvim', 'nano', 'kak']:
answer, selection_idx, err = _hkml_cli.ask_selection(
desc='Seems you are replying on a mail that ' \
'you were reading with the cursor on %d-th row. ' \
'Shall I put the cursor on the reply writing screen ' \
'on the same row, ' \
'so that you can write reply from there?' % cursor_row,
selections_txt=[
'Yes, put cursor on the %d-th row' % cursor_row,
'No, put the cursor on the first row'],
default_selection_idx=0, allow_cancel=False, allow_error=False)
if selection_idx == 0:
cmd = [editor, '+%d' % cursor_row, file_path]
vim_cmds = [
'''highlight past ctermfg=darkblue guifg=darkblue''',
'''match past /^> >.*$/''',
'''highlight orig ctermfg=darkgreen guifg=darkgreen''',
'''2match orig /^> \\([^>].*\\|\\)$/''',
]
# kakoune executes the '-e' argument as commands on client initialisation
kak_cmds = [
'''add-highlighter window/past regex '^> >.*$' 0:blue''',
'''add-highlighter window/orig regex '^> ([^>].*|)$' 0:green''',
]
if target_desc == 'mail' and editor in ['vim', 'nvim', 'kak']:
answer, selection, err = _hkml_cli.ask_selection(
desc=''.join([
'On %s, I can show lines from past mails ' % editor,
'and this mail with blue and green colors. ',
'That may help you finding lines to write reply for. ',
'Shall I open the editor with the coloring?'
]),
selections_txt=['Yes, do the past lines coloring.',
'No, I\'m good.'],
default_selection_idx=0,
allow_cancel=False, allow_error=False)
if selection == 0:
options = []
if editor == 'kak':
options += ['-e', '; '.join(kak_cmds)]
notice = coloring_notice_kak
else:
for vim_cmd in vim_cmds:
options.append('-c')
options.append(vim_cmd)
notice = coloring_notice_vim
cmd += options
add_coloring_explanation(file_path, notice)
if subprocess.call(cmd) != 0:
return 'The editor for %s exit with an error.' % target_desc
return None
def write_send_mail(draft_mail, subject, in_reply_to, to, cc, body, attach,
format_only):
mbox = format_mbox(subject, in_reply_to, to, cc, body, None, draft_mail,
attach)
if format_only:
print(mbox)
return
fd, tmp_path = tempfile.mkstemp(prefix='hkml_mail_')
with open(tmp_path, 'w') as f:
f.write(mbox)
err = open_editor(tmp_path)
if err is not None:
print(err)
exit(1)
orig_draft_subject = None
if draft_mail:
orig_draft_subject = draft_mail.subject
hkml_send.send_mail(tmp_path, get_confirm=True, erase_mbox=True,
orig_draft_subject=orig_draft_subject)
def main(args):
draft_mail = None
if args.draft is not None:
draft_mail = _hkml_list_cache.get_mail(args.draft)
if draft_mail is None:
print('failed getting draft mail of the index.')
exit(1)
write_send_mail(draft_mail, args.subject, args.in_reply_to, args.to,
args.cc, args.body, args.attach, args.format_only)
def add_common_arguments(parser):
parser.add_argument('--attach', metavar='<file>', nargs='+',
help='file to paste at the end of the body')
parser.add_argument('--format_only', action='store_true',
help='print formatted mail template only')
def set_argparser(parser=None):
parser.description = 'write a mail'
parser.add_argument('--subject', metavar='<subject>', type=str,
help='Subject of the mail.')
parser.add_argument('--in-reply-to', metavar='<message id>',
help='Add in-reply-to field in the mail header')
parser.add_argument('--to', metavar='<email address>', nargs='+',
help='recipients of the mail')
parser.add_argument('--cc', metavar='<email address>', nargs='+',
help='cc recipients of the mail')
parser.add_argument('--body', metavar='<body>',
help='body message of the mail')
parser.add_argument('--draft', metavar='<index>', type=int,
help='resume writing from the given draft')
add_common_arguments(parser)