) {
396 | next if defined $seen{$_};
397 | $seen{$_} = 1;
398 | $hash{$_} = 1;
399 | $addrcount++;
400 | print O $_;
401 | print A $_;
402 | }
403 | close J;
404 | }
405 | my $database_path = qx/notmuch config get database.path/;
406 | chomp $database_path;
407 | my @exclude_re = map qr(^$database_path/$_), @exclude;
408 | print((join "\n", map "Excluding '^$database_path/$_'", @exclude), "\n")
409 | if @exclude and $o_rebuild;
410 | undef $database_path;
411 | undef @exclude;
412 |
413 | my $ptime = $sometime + 5;
414 | $| = 1;
415 | my $efn = tempfile(DIR => $configdir);
416 | open P, '-|', qw/notmuch search --sort=newest-first --output=files/, $sstr;
417 | X: while () {
418 | foreach my $re (@exclude_re) { next X if /$re/; }
419 | print $efn $_;
420 | }
421 | close P;
422 | seek $efn, 0, 0;
423 |
424 | while (<$efn>) {
425 | chomp;
426 | # open in raw mode to avoid fatal utf8 problems. does some conversion
427 | # heuristics like latin1 -> utf8 there... -- _utf8_on used on need basis.
428 | open M, '<:raw', $_ or next;
429 | while () {
430 | last if /^\s*$/;
431 | next unless s/^(From|To|Cc|Bcc):\s+//i;
432 | s/\s+$//;
433 | my @a = ( $_ );
434 | while () {
435 | # XXX leaks to body in case empty line is found in this loop...
436 | # XXX Note that older code leaked to mail body always...
437 | if (s/^\s+// or s/^(From|To|Cc|Bcc):\s+/,/i) {
438 | s/\s+$//;
439 | push @a, $_;
440 | next;
441 | }
442 | last;
443 | }
444 | $_ = join ' ', @a;
445 |
446 | if (time > $ptime) {
447 | my $c = qw(/ - \ |)[int ($ptime / 5) % 4];
448 | print $c, ' active addresses gathered: ', $addrcount, "\r";
449 | $ptime += 5;
450 | }
451 |
452 | # The parse function from Email::Address heavily modified
453 | # to fit ok in this particular purpose. New bugs are mine!
454 | # --8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<--
455 |
456 | s/[ \t]+/ /g; # this line did fail fatally on malformed utf-8 data...
457 | s/\?= =\?/\?==\?/g;
458 | my (@mailboxes) = (/$mailbox/go);
459 | L: foreach (@mailboxes) {
460 | next if defined $seen{$_};
461 | $seen{$_} = 1;
462 |
463 | my @comments = /($comment)/go;
464 | s/$comment//go if @comments;
465 |
466 | my ($user, $host);
467 | ($user, $host) = ($1, $2) if s/<($local_part)\@($domain)>//o;
468 | if (! defined($user) || ! defined($host)) {
469 | s/($local_part)\@($domain)//o;
470 | ($user, $host) = ($1, $2);
471 | }
472 |
473 | sub decode_substring ($) {
474 | my $t = lc $2;
475 | my $s;
476 | if ($t eq 'b') { $s = decode_base64($3); }
477 | elsif ($t eq 'q') { $s = decode_qp($3); }
478 | else {
479 | $_[0] = 0;
480 | return "=?$1?$2?$3?=";
481 | }
482 | $s =~ tr/_/ /;
483 |
484 | return decode_utf8($s) if lc $1 eq 'utf-8';
485 |
486 | my $o = find_encoding($1);
487 | $_[0] = 0, return "=?$1?$2?$3?=" unless ref $o;
488 | return $o->decode($s);
489 | }
490 | sub decode_data () {
491 | my $loopmax = 5;
492 | while ( s{ =\?([^?]+)\?(\w)\?(.*?)\?= }
493 | { decode_substring($loopmax) }gex ) {
494 | last if --$loopmax <= 0;
495 | };
496 | }
497 |
498 | my @phrase = /($display_name)/o;
499 | decode_data foreach (@phrase);
500 |
501 | for ( @phrase, $host, $user, @comments ) {
502 | next unless defined $_;
503 | s/^[\s'"]+//; ## additions 20111123 too
504 | s/[\s'"]+$//; ## additions 20111123 too
505 | $_ = undef unless length $_;
506 | }
507 | # here we want to have email address always // 20111123 too
508 | next unless defined $user and defined $host;
509 |
510 | my $userhost = lc "<$user\@$host>";
511 | #my $userhost = "<$user\@$host>";
512 |
513 | @comments = grep { defined or return 0; decode_data; 1; } @comments;
514 |
515 | # "trim" phrase, if equals to user@host after trimming, drop it.
516 | if (defined $phrase[0]) {
517 | #$phrase[0] =~s/\A"(.+)"\z/$1/;
518 | $phrase[0] =~ tr/\\//d; ## 20111124 too
519 | $phrase[0] =~ s/\"/\\"/g;
520 | @phrase = () if lc "<$phrase[0]>" eq $userhost;
521 | }
522 |
523 | # In case we would have {phrase} (comment),
524 | # make that "{phrase} (comment)" ...
525 | if (defined $phrase[0])
526 | {
527 | if ($o_ncm and $phrase[0] =~ /^(.*)\s*,\s*(.*)$/) {
528 | # Try to change "Last, First" to "First Last"
529 | # The heuristics: If either 'Last' or 'First' is having
530 | # the same length in name and address and case-insensitive
531 | # comparison where characters not matching a-z ignored.
532 | my ($mlast, $mfirst) = ($1, $2);
533 | if ($userhost =~ /^([^.]+)[.]([^@]+)@/) {
534 | my ($afirst, $alast) = ($1, $2);
535 | #print "$mlast - $mfirst / $afirst, $alast\n";
536 | if (_utf8_on($mlast),
537 | length $mlast == length $alast) {
538 | my $re = $mlast; $re =~ tr/A-Za-z/./c;
539 | $phrase[0] = "$mfirst $mlast" if $alast =~ /$re/i;
540 | }
541 | elsif (_utf8_on($mfirst),
542 | length $mfirst == length $afirst) {
543 | my $re = $mfirst; $re =~ tr/A-Za-z/./c;
544 | $phrase[0] = "$mfirst $mlast" if $afirst =~ /$re/i;
545 | }
546 | }
547 | }
548 |
549 | if (@comments) {
550 | $phrase[0] = qq/"$phrase[0] / . join(' ', @comments) . '"';
551 | @comments = ();
552 | }
553 | else {
554 | $phrase[0] = qq/"$phrase[0]"/;
555 | }
556 | }
557 | else {
558 | @phrase = ();
559 | }
560 | $_ = join(' ', @phrase, $userhost, @comments) . "\n";
561 | next if defined $hash{$_};
562 | print O $_;
563 | $hash{$_} = 1;
564 | next if defined $ign_hash{$_};
565 | foreach my $re (@ign_relist) {
566 | next L if $_ =~ $re;
567 | }
568 | print A $_;
569 | $addrcount++;
570 | }
571 | # --8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<--
572 | }
573 | close M;
574 | }
575 | undef %seen;
576 | close $efn;
577 | my $oldaddrcount = 0;
578 | if (defined fileno I) {
579 | $sstr = '*'; # XXX, to be fixed...
580 | L: while () {
581 | last if /^---/;
582 | next if defined $hash{$_};
583 | print O $_;
584 | next if defined $ign_hash{$_};
585 | foreach my $re (@ign_relist) {
586 | next L if $_ =~ $re;
587 | }
588 | print A $_;
589 | $addrcount++;
590 | }
591 | while () {
592 | $oldaddrcount = ($1 + 0), next if /^active:\s+(\d+)\s*$/;
593 | }
594 | close I;
595 | }
596 | print O "---\n";
597 | print O "active: ", $addrcount, "\n";
598 | close O;
599 | close A;
600 | undef %hash;
601 | #link $adbpath, $adbpath . '.' . $sometime;
602 | rename $adbpath . '.new', $adbpath or
603 | die "Cannot rename '$adbpath.new' to '$adbpath': $!\n";
604 | rename $actpath . '.new', $actpath or
605 | die "Cannot rename '$actpath.new' to '$actpath': $!\n";
606 | if ($oldaddrcount or $sstr eq '*') {
607 | $sometime = time - $sometime;
608 | my $new = $addrcount - $oldaddrcount;
609 | print "Added $new active addresses in $sometime seconds.\n";
610 | }
611 | print "Total number of active addresses: $addrcount.\n";
612 | exit 0;
613 |
614 | __END__
615 |
616 | =encoding utf8
617 |
618 | =head1 NAME
619 |
620 | nottoomuch-addresses.sh -- address completion/matching (for notmuch)
621 |
622 | =head1 SYNOPSIS
623 |
624 | nottoomuch-addresses.sh (--update | --rebuild [opts] | )
625 |
626 | B for more help
627 |
628 | =head1 VERSION
629 |
630 | 2.5 (2020-10-03)
631 |
632 | =head1
633 |
634 | In case no option argument is given on command line, the command line
635 | arguments are used as fixed search string. Search goes through all
636 | email addresses in cache and outputs every address (separated by
637 | newline) where a substring match with the given search string is
638 | found. No wildcard of regular expression matching is used.
639 |
640 | Search is not done unless there is at least 3 octets in search string.
641 |
642 | =head1 OPTIONS
643 |
644 | =head2 B<--update>
645 |
646 | This option is used to incrementally update the "address cache" with
647 | new addresses that are available in mails received since last update.
648 |
649 | All the sender and receiver addresses are collected.
650 |
651 | The addresses in cache are sorted "newest first"; recently seen addresses
652 | are matched first when doing searches (but see also the "top" file section
653 | below).
654 |
655 | =head2 B<--rebuild>
656 |
657 | With this option the address cache is created (or rebuilt from scratch).
658 |
659 | In addition to initial creation this option is useful when some build
660 | options (which affect to all addresses) are desired to be changed.
661 |
662 | Sometimes some of the new emails received may have Date: header point too
663 | much in the past (one week before last update). Update uses email Date:
664 | information to go through new emails to be checked for new addresses
665 | with one week's overlap, and only rebuild will catch these emails (albeit
666 | the rebuild option is quite heavy option to solve such a problem).
667 |
668 | =head3 B<--rebuild> options:
669 |
670 | When (re)building the address cache, there are a few options to affect
671 | the operation (and future additions).
672 |
673 | =over 2
674 |
675 | =item B<--since>=YYYY-MM-DD
676 |
677 | Start email gathering from mails dated YYYY-MM-DD. I.e. skip older.
678 |
679 | =item B<--exclude-path-re>=path-regexp
680 |
681 | Regular expression(s) of directory paths to exclude when scanning mail files.
682 | This option can be given multiple times on the command line.
683 |
684 | Given regexps are anchored to the start of the string (based on the email
685 | directory notmuch is configured with), but not to the end (for example to
686 | match anywhere prefix regexp with '.*', or conversely, to anchor end suffix
687 | regexp with '$').
688 |
689 | =item B<--name-conversion>=lcf2flem
690 |
691 | With name conversion method 'lcf2flem' (the only method known) email addresses
692 | in format "Last, First " are converted to
693 | "First Last ". For this conversion to succeed either
694 | "First" or "Last" needs to match the corresponding string in email address.
695 | If there are non-us-ascii characters in the names those are ignored in
696 | comparisons (i.e. matches any character).
697 |
698 | This method name is modeled from
699 | "Last-comma-First-to-First-Last-either-matches".
700 |
701 | =back
702 |
703 | =head1 "TOP" FILE
704 |
705 | Some of the recipient addresses you may want to use are not collected from
706 | the emails just in the format you liked. Also some of the addresses you
707 | desire to be found first may get buried deeper in the list of collected
708 | addresses.
709 |
710 | When running B<--update> the output shows the path of address cache
711 | file (usually C<$HOME/.config/nottoomuch/addresses>). If there is file
712 | C in the same directory, that file is read as
713 | newline-separated list of addresses which are to be included on the top
714 | of the address cache file.
715 |
716 | =head1 IGNORE FILE
717 |
718 | Some of the addresses collected may be valid but those still seems to
719 | be noisy junk. You may additionally want to just hide some email
720 | addresses.
721 |
722 | If there is file C in the same directory as C
723 | (and perhaps C), that file is read as newline-separated list
724 | of addresses which are *not* to be included in the address cache file.
725 |
726 | Use your text editor to open both of these files. Then move address
727 | lines to be ignored from B to B. After
728 | saving these 2 files the moved addresses will not reappear in
729 | B file again.
730 |
731 | Since 2012 nottoomuch-addresses.sh has supported regular expressions in
732 | ignore file. Lines in format I or I defines (perl)
733 | Is which are used to match email addresses for ignoring. The
734 | I format makes regular expression case-insensitive -- although this
735 | is only applied to characters in ranges I and I. Remember that
736 | I^.*regexp.*$/> and I provides same set of matching lines.
737 |
738 | =head1 LICENSE
739 |
740 | This program uses code from Email::Address perl module. Inclusion of
741 | that makes it easy to define license for the whole of this code base:
742 |
743 | Terms of Perl itself
744 |
745 | a) the GNU General Public License as published by the Free
746 | Software Foundation; either version 1, or (at your option) any
747 | later version, or
748 |
749 | b) the "Artistic License"
750 |
751 | =head1 SEE ALSO
752 |
753 | L, L
754 |
755 | =head1 AUTHOR
756 |
757 | Tomi Ollila -- too ät iki piste fi
758 |
759 | =head1 ACKNOWLEDGMENTS
760 |
761 | This program uses code from Email::Address, Copyright (c) by Casey West
762 | and maintained by Ricardo Signes. Thank you. All new bugs are mine,
763 | though.
764 |
--------------------------------------------------------------------------------
/nottoomuch-emacs-mailto.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | # -*- mode: cperl; cperl-indent-level: 4 -*-
3 | # $ nottoomuch-emacs-mailto.pl $
4 | #
5 | # Author: Tomi Ollila -- too ät iki piste fi
6 | #
7 | # Copyright (c) 2014-2019 Tomi Ollila
8 | # All rights reserved
9 | #
10 | # Created: Sun 29 Jun 2014 16:20:24 EEST too
11 | # Last modified: Sat 09 Jan 2021 00:25:50 +0200 too
12 |
13 | # Handle mailto: links with notmuch emacs client. In case of
14 | # graphical display (the usual case), use emacs in server mode
15 | # (with special server socket) and run emacsclient on it.
16 | # On non-graphical terminal run emacs in terminal mode.
17 | # For special use cases (e.g. for wrappers) there are some extra
18 | # command line options (currently -nw and --from=).
19 |
20 | # There are some hacks involved to get things working, and may break in the
21 | # future. However, my guess is that this will just work for years to come.
22 |
23 | use 5.8.1;
24 | use strict;
25 | use warnings;
26 | use Cwd;
27 |
28 | # override "default" From: (by setting $from to non-empty string) if desired
29 | my $from = '';
30 |
31 | # fyi: default emacs server socket name is 'server' (in /tmp/emacs$UID/)...
32 | my $socket_name = 'mailto-server';
33 |
34 | #open O, '>', "/tmp/mmm.$$"; print O "@ARGV\n"; close O;
35 |
36 | while (@ARGV) {
37 | $_ = $ARGV[0];
38 | delete $ENV{DISPLAY}, shift, next if $_ eq '-nw';
39 | $from=$1, shift, next if /^--from=(.*)/;
40 | $ENV{EMACS} = $ENV{EMACSCLIENT} = 'echo', shift, next if $_ eq '--dry-run';
41 | last;
42 | }
43 |
44 | die "Usage: $0 [-nw] [--from=address] mailto-url\n" unless @ARGV;
45 |
46 | my $use_emacsclient = defined $ENV{'DISPLAY'} && $ENV{DISPLAY} ne '';
47 |
48 | sub mail($$)
49 | {
50 | my $rest;
51 | ($_, $rest) = split /\?/, $_[1], 2;
52 | warn("skipping '$_' (does not start with 'mailto:')\n"), return
53 | unless s/^mailto://; #s/\s+//g;
54 | my %hash = ( to => [], subject => [], cc => [], bcc => [],
55 | 'in-reply-to' => [], keywords => [], body => [] );
56 | push @{$hash{to}}, $_ if $_;
57 | if (defined $rest) {
58 | foreach (split /&/, $rest) {
59 | my $hfname;
60 | ($hfname, $_) = split /=/, $_, 2;
61 | $hfname = lc $hfname;
62 | next unless defined $hash{$hfname};
63 | s/%([\da-fA-F][\da-fA-F])/chr(hex($1)) unless lc($1) eq '0d'/ge;
64 | #s/%([\da-fA-F][\da-fA-F])/chr(hex($1))/ge;
65 | s/(["\\])/\\$1/g;
66 | push @{$hash{$hfname}}, $_;
67 | }
68 | }
69 | $" = ', ';
70 | sub liornil($) {
71 | no warnings; # maybe the warning is effective when %hash reassigned ?
72 | return @{$hash{$_[0]}}? "\"@{$hash{$_[0]}}\"": "nil";
73 | }
74 | my $to = liornil 'to';
75 | my $subject = liornil 'subject';
76 | my $other_hdrs = 'nil';
77 | if ($from) {
78 | $from =~ s/("|\\)/\\$1/g;
79 | $other_hdrs = "'((From . \"$from\")";
80 | }
81 | if (@{$hash{'in-reply-to'}}) {
82 | my $m = "@{$hash{'in-reply-to'}}";
83 | $other_hdrs = "'(" if $other_hdrs eq 'nil';
84 | $other_hdrs .= "(In-Reply-to . \"$m\")";
85 | }
86 | $other_hdrs .= ')' if $other_hdrs ne 'nil';
87 |
88 | my @elisp = ( "(with-demoted-errors", " (require 'notmuch)",
89 | " (notmuch-mua-mail",
90 | " $to",
91 | " $subject",
92 | " $other_hdrs",
93 | " (notmuch-mua-get-switch-function))" );
94 | sub ideffi($) {
95 | no warnings; # ditto, now with @elisp too...
96 | return unless @{$hash{$_[0]}};
97 | push @elisp, " (message-goto-$_[0]) (insert \"@{$hash{$_[0]}}\")";
98 | }
99 | ideffi 'cc';
100 | ideffi 'bcc';
101 | ideffi 'keywords';
102 | $" = "\n";
103 |
104 | if (@{$hash{body}}) {
105 | # hacking body addition just before signature setup (since message
106 | # setup hook, which is called later, may add e.g. MML header[s])
107 | splice @elisp, 2, 0, (
108 | ' (let ((message-signature-setup-hook message-signature-setup-hook))',
109 | " (add-hook 'message-signature-setup-hook",
110 | qq' (lambda () (message-goto-body) (insert "@{$hash{body}}")',
111 | ' (if (/= (point) (line-beginning-position))',
112 | ' (newline))))' )
113 | }
114 | if ($use_emacsclient) {
115 | my $cwd = cwd(); $cwd =~ s/("|\\)/\\$1/g;
116 | push @elisp, qq' (cd "$cwd")';
117 | }
118 | my @cmdline;
119 |
120 | if ($use_emacsclient) {
121 | my $emacsclient = $ENV{EMACSCLIENT} || 'emacsclient';
122 | @cmdline = ( $emacsclient,
123 | qw/-c --alternate-editor= --no-wait -s/, $socket_name );
124 | # code to stop emacs if all frames are closed, there are no
125 | # clients and no modified buffers which have file name
126 | splice @elisp, 2, 0, (
127 | ' (defun delete-mailto-frame-function (frame)
128 | (if (and (= (length server-clients) 0)
129 | (< (length (delq frame (frame-list))) 2)
130 | (not (memq t (mapcar (lambda (buf) (and (buffer-file-name buf)
131 | (buffer-modified-p buf)))
132 | (buffer-list)))))
133 | (kill-emacs)))',
134 | " (add-hook 'delete-frame-functions 'delete-mailto-frame-function)" );
135 | }
136 | else {
137 | my $emacs = $ENV{EMACS} || 'emacs';
138 | @cmdline = ( $emacs, '-nw' );
139 | }
140 | push @elisp, '))';
141 | push @cmdline, '--eval', "@elisp";
142 |
143 | #print "@cmdline\n"; exit 0;
144 | exec @cmdline unless $_[0];
145 | system @cmdline;
146 | }
147 |
148 | my $last = pop @ARGV;
149 | mail 1, $_ foreach (@ARGV);
150 | mail 0, $last;
151 |
--------------------------------------------------------------------------------
/nottoomuch-emacs-mailto.rst:
--------------------------------------------------------------------------------
1 | nottoomuch-emacs-mailto.pl
2 | ==========================
3 |
4 | *nottoomuch-emacs-mailto.pl* is a tool typically used from web browser
5 | ``mailto:`` links to send email using notmuch emacs client.
6 |
7 | When used in graphical environment, a new emacs frame is started
8 | and filled with the information provided in ``mailto:`` link. After
9 | sending the frame will stay on desktop and can be closed the usual
10 | emacs way (*).
11 |
12 | On non-graphical terminal, new emacs process is started on the terminal
13 | and from there it works like in graphical display.
14 |
15 | In addition to taking ``mailto:`` arguments from command line, there are
16 | (currently) 2 other options, ``-nw`` and ``--from=``. These are,
17 | and can be used in special applications (e.g. wrappers) to e.g. get some
18 | work done.
19 |
20 | How To "Install"
21 | ----------------
22 |
23 | 1. Copy `nottoomuch-emacs-mailto.pl `_ to
24 | the machine you intent to use it (or clone this repository).
25 |
26 | 2. Configure web browser to use this when following ``mailto:`` links.
27 | In Firefox this was easy: *Edit->Preferences->Applications* and set
28 | ``mailto`` there. In Chrome I could not find how this is done;
29 | perhaps some mystic *xdg* things or something... I'll update this
30 | whenever I figure out... OK, the solution is to use
31 | nottoomuch-xdg-email_ to wrap ``xdg-email``... see its embedded
32 | documentation for more info.
33 |
34 | .. _nottoomuch-xdg-email: nottoomuch-xdg-email.sh
35 |
36 | (*) In graphical display, emacs is started in server mode and the frame
37 | is opened using ``emacsclient(1)``. The emacs server uses special server
38 | socket named ``mailto-server``. When last frame closes, there are no
39 | clients connected and no modified buffers with file name, the emacs
40 | in daemon mode exits.
41 |
--------------------------------------------------------------------------------
/nottoomuch-remote-emacs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | :; export REMOTE_NOTMUCH_SSHCTRL_SOCK=${1##*/}; shift
3 | :; exec "${EMACS:-emacs}" --debug-init --load "$0" "$@"; exit
4 |
5 | ;; wrapper to set up remote notmuch for emacs; used with nottoomuch-remote.bash
6 |
7 | ;; setting REMOTE_NOTMUCH_SSHCTRL_SOCK from command line has been put
8 | ;; into the shell script part so that it does not stay in ps output
9 |
10 | ;; alternatively, this file can be copied as new one, and embed the socket
11 | ;; path in. in that case it is also convenient to add more eval-after-load
12 | ;; settings (e.g. mail send configurations)
13 |
14 | ;; (setenv "REMOTE_NOTMUCH_SSHCTRL_SOCK" "master-notmuch@remote:22")
15 |
16 | (setq sshctrl-sock (getenv "REMOTE_NOTMUCH_SSHCTRL_SOCK"))
17 |
18 | (when (string-equal sshctrl-sock "")
19 | (insert "\nUsage: " (file-name-nondirectory load-file-name) " {ctl_name} "
20 | "[other emacs options]\n"
21 | "\n{cl_name} is ssh control socket located in ~/.ssh/...")
22 | (error "missing argument"))
23 |
24 | (setq ctl_path (concat (expand-file-name "~/.ssh/") sshctrl-sock))
25 |
26 | (unless (file-exists-p ctl_path)
27 | (insert "\nSocket '" ctl_path "' does not exist!")
28 | (error "missing file"))
29 |
30 | ;; XXX common cases -- don't know how to check for socket
31 | (when (or (file-regular-p ctl_path) (file-directory-p ctl_path))
32 | (insert "\nFile '" ctl_path "' is not socket!")
33 | (error "file not socket"))
34 |
35 | (eval-after-load "notmuch"
36 | (lambda ()
37 | (setq notmuch-command (concat (file-name-directory load-file-name)
38 | "nottoomuch-remote.bash"))
39 | ;; add more in your own copy, if desired
40 | ))
41 |
42 | (load "notmuch")
43 |
44 | (insert "
45 | To start notmuch (hello) screen, evaluate
46 | (notmuch-hello) <- type C-x C-e or C-j between \")\" and \"<-\"")
47 |
48 | ;; Local Variables:
49 | ;; mode: emacs-lisp
50 | ;; End:
51 |
--------------------------------------------------------------------------------
/nottoomuch-remote.bash:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # -*- shell-script -*-
3 | #
4 | # Created: Tue 29 May 2012 21:30:17 EEST too
5 | # Last modified: Sun 19 Dec 2021 11:59:37 +0200 too
6 |
7 | # See first ./nottoomuch-remote.rst and then maybe:
8 | # http://notmuchmail.org/remoteusage/
9 | # http://notmuchmail.org/remoteusage/124/ <- this script
10 |
11 | set -euf
12 | # To trace execution, uncomment next line:
13 | #exec 6>>remote-errors; BASH_XTRACEFD=6; echo -- >&6; set -x; env >&6
14 |
15 | : ${REMOTE_NOTMUCH_SSHCTRL_SOCK:=master-notmuch@remote:22}
16 | : ${REMOTE_NOTMUCH_COMMAND:=notmuch}
17 |
18 | readonly REMOTE_NOTMUCH_SSHCTRL_SOCK REMOTE_NOTMUCH_COMMAND
19 |
20 | SSH_CONTROL_ARGS='-oControlMaster=no -S ~'/.ssh/$REMOTE_NOTMUCH_SSHCTRL_SOCK
21 | readonly SSH_CONTROL_ARGS
22 |
23 | printf -v ARGS '%q ' "$@" # bash feature
24 | readonly ARGS
25 |
26 | if ssh -q $SSH_CONTROL_ARGS .1 "$REMOTE_NOTMUCH_COMMAND" $ARGS
27 | then exit 0
28 | else ev=$?
29 | fi
30 |
31 | # continuing here in case ssh exited with nonzero value
32 |
33 | case $* in
34 | 'config get user.primary_email') echo 'nobody@nowhere.invalid'; exit 0 ;;
35 | 'config get user.name') echo 'nobody'; exit 0 ;;
36 | 'count'*'--batch'*) while read line; do echo 1; done; exit 0 ;;
37 | 'count'*) echo 1; exit 0 ;;
38 | 'search-tags'*) echo 'errors'; exit 0 ;;
39 | 'search'*'--output=tags'*) echo 'errors'; exit 0 ;;
40 | esac
41 |
42 | # fallback exit handler; print only to stderr...
43 | exec >&2
44 |
45 | if ssh $SSH_CONTROL_ARGS -O check 0.1
46 | then
47 | echo " Control socket is alive but something exited with status $ev"
48 | exit $ev
49 | fi
50 |
51 | case $0 in */*) dn0=${0%/*} ;; *) dn0=. ;; esac
52 | echo "See $dn0/nottoomuch-remote.rst for more information"
53 |
54 | exit $ev
55 |
--------------------------------------------------------------------------------
/nottoomuch-remote.rst:
--------------------------------------------------------------------------------
1 | Notmuch remoteusage without password-free login requirement
2 | ===========================================================
3 |
4 | This solution uses one pre-made ssh connection where the client is put into
5 | "master" mode (-M) for connection sharing. The wrapper script then uses the
6 | control socket created by this pre-made ssh connection for its own
7 | connection. As long as master ssh connection is live, slave can use
8 | it. Disconnecting master all future attempts to connect from the script
9 | will fail.
10 |
11 | The script
12 | ----------
13 |
14 | Is available at `nottoomuch-remote.bash `_
15 |
16 | While viewing the script, notice the ``0.1`` in ssh command line. It is
17 | used to avoid any opportunistic behaviour ssh might do; for example if
18 | control socket is not alive ssh would attempt to do it's own ssh connection
19 | to remote ssh server. As address ``0.1`` is invalid this attempt will fail
20 | early.
21 |
22 | Test
23 | ----
24 |
25 | Easiest way to test this script is to run the pre-made ssh connection using
26 | the following command line:
27 | ::
28 | $ ssh -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost sleep 600
29 |
30 | (replace ``[user@]remotehost`` above with your login info). Doing this the
31 | above wrapper script can be run unmodified. After the above command has
32 | been run on one terminal, enter ``chmod +x nottoomuch-remote.bash`` in another
33 | terminal and then test the script with
34 | ::
35 | $ ./nottoomuch-remote.bash help
36 |
37 | Note that the '~' in the ssh command line above is inside single quotes for
38 | a reason. In this case shell never expand it to $HOME -- ssh does it by not
39 | reading $HOME but checking the real user home directory from
40 | ``/etc/passwd``. For security purposes this is just how it should be.
41 |
42 | Tune
43 | ----
44 |
45 | The path ``'~'/.ssh/master-notmuch@remote:22`` might look too generic to be
46 | used as is as the control socket after initial testing (but it can be used).
47 | It is presented as a template for what could be configured to
48 | ``$HOME/.ssh/config``. For example:
49 | ::
50 | Host *
51 | ControlPath ~/.ssh/master-%h@%p:%r
52 |
53 | is a good entry to have been written in ``$HOME/.ssh/config``.
54 | Now, let's say you'd make your pre-made ssh connection with command
55 | ::
56 | $ ssh -M robin@example.org
57 |
58 | There is 3 options how to handle this with ``./nottoomuch-remote.bash``:
59 |
60 | 1) Edit ``./nottoomuch-remote.bash`` and change ``REMOTE_NOTMUCH_SSHCTRL_SOCK``
61 | to contain the new value (being *master-robin@example.org:22* in this
62 | case)
63 |
64 | 2) Make symlink:
65 | ``$ ln -sfT master-robin@example.org:22 ~/.ssh/master-notmuch@remote:22``
66 |
67 | 3) ``REMOTE_NOTMUCH_SSHCTRL_SOCK`` can be used via environment; like:
68 | ::
69 | $ REMOTE_NOTMUCH_SSHCTRL_SOCK=master-robin@example.org:22 ./nottoomuch-remote.bash help
70 |
71 | Alternative 3 provides way to use remote notmuch without editing
72 | nottoomuch-remote.bash -- also the same script can be used with multiple
73 | clients to separate (local +) remotes simultaneously!
74 |
75 | 4) Use the new script, ``nottoomuch-remote-emacs.sh`` briefly mentioned
76 | below. More information of it will be added later...
77 |
78 | Configure Emacs on the client computer
79 | --------------------------------------
80 |
81 | (there is now a new tool available:
82 | `nottoomuch-remote-emacs.sh `_
83 | use it or this other stuff below...)
84 |
85 | Add something like the following functions to your Emacs (general(*) or
86 | notmuch specific) configuration files:
87 | ::
88 | ;; this should work as backend function when copied verbatim
89 | (defun user/notmuch-remote-setup (sockname)
90 | (setq notmuch-command "/path/to/nottoomuch-remote.bash")
91 | (setenv "REMOTE_NOTMUCH_SSHCTRL_SOCK" sockname)
92 | ;; If you use Fcc, you may want to do something like this on the client,
93 | ;; to Bcc mails to yourself (if not, remove in your implementation):
94 | (setq notmuch-fcc-dirs nil)
95 | (add-hook 'message-header-setup-hook
96 | (lambda () (insert (format "Bcc: %s <%s>\n"
97 | (notmuch-user-name)
98 | (notmuch-user-primary-email))))))
99 |
100 | ;; this is just an example to configure using "default" master socket
101 | (defun user/notmuch-remote-default ()
102 | (interactive)
103 | (user/notmuch-remote-setup "master-notmuch@remote:22")
104 |
105 | ;; usage example2: set USER & HOST1 according to your remote...
106 | (defun user/notmuch-remote-at-HOST1 ()
107 | (interactive)
108 | (user/notmuch-remote-setup "master-USER@HOST1:22")
109 |
110 | ;; ... you probably got the point now -- add relevant funcs to your config
111 | (defun user/notmuch-remote-at-HOST2 ()
112 | (interactive)
113 | (user/notmuch-remote-setup "master-USER@HOST2:22")
114 |
115 | ... and if you want to activate your remote by default just call
116 | ``(user/notmuch-remote-setup "master-USER@HOST:22")`` without function call
117 | wrapper.
118 |
119 | (*) general most likely being ~/.emacs
120 |
121 | Creating master connection
122 | --------------------------
123 |
124 | **(Note: all the examples below use the default master socket written in**
125 | ``./nottoomuch-remote.bash`` **for initial test easiness; remove/change the**
126 | ``-S '~'/.ssh/master-notmuch@remote:22`` **in case you don't need it.)**
127 |
128 | As mentioned so many times, using this solution requires one pre-made ssh
129 | connection in *master* mode. The simplest way is to dedicate one terminal
130 | for the connection with shell access to the remote machine:
131 | ::
132 | $ ssh -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost
133 |
134 | One possibility is to have this dedicated terminal in a way that the
135 | connection has (for example 1 hour) timeout:
136 | ::
137 | $ ssh -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost sleep 3600
138 |
139 | The above holds the terminal. The next alternative puts the command in
140 | background:
141 | ::
142 | $ ssh -f -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost sleep 3600
143 |
144 | If you don't want this to timeout so soon, use a longer sleep, like
145 | 99999999 (8 9:s, 1157 days, a bit more than 3 years).
146 |
147 | A more "exotic" solution would be to make a shell script running on remote
148 | machine, checking/inotifying when new mail arrives. When mail arrives it
149 | could send message back to local host, where a graphical client (to be
150 | written) pops up on display providing info about received mail (and exiting
151 | this graphical client connection to remote host is terminated).
152 |
153 | Troubleshooting
154 | ---------------
155 |
156 | If you experience strange output when using from emacs first attempt to
157 | just run
158 | ::
159 | $ ./nottoomuch-remote.bash help
160 |
161 | from command line and observe output. If it looks as it should be next
162 | uncomment the line
163 | ::
164 | #exec 6>>remote-errors; BASH_XTRACEFD=6; echo -- >&6; set -x
165 |
166 | in ``./nottoomuch-remote.bash`` and attempt to use it from emacs again --
167 | and then examine the contents of remote-errors in the working directory
168 | emacs was started.
169 |
--------------------------------------------------------------------------------
/nottoomuch-xdg-email.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # wrap system xgd-email to run nottoomuch-emacs-mailto.pl for mailto: links
4 | # for those (including me) who don't know (or want or cannot) how to configure
5 | # xdg-email to run specific email program.
6 |
7 | # these steps usually work:
8 | #
9 | # $ mkdir $HOME/bin
10 | # $ cd $HOME/bin
11 | # $ ln -s ../path/to/nottoomuch/nottoomuch-emacs-mailto.pl .
12 | # $ ln -s ../path/to/nottoomuch/nottoomuch-xdg-email.sh xdg-email
13 |
14 | # use MAILER=echo /usr/bin/xdg-email ... to test how system xdg-email works...
15 |
16 | set -euf
17 |
18 | MAILER=`command -v nottoomuch-emacs-mailto.pl`
19 | #if test -h "$MAILER"
20 | #then MAILER=`exec readlink -f "$mailer"`
21 | #fi
22 | export MAILER
23 |
24 |
25 | #saved_IFS=$IFS
26 | IFS=: # this setting does not affect expansion of "$@" (vs "$*")
27 |
28 | for d in $PATH
29 | do if test -x "$d"/xdg-email
30 | then
31 | test "$0" -ef "$d"/xdg-email || exec "$d"/xdg-email "$@"
32 | fi
33 | done
34 |
35 | exec >&2
36 | echo Cannot find xdg-email
37 | exit 1
38 |
--------------------------------------------------------------------------------
/selection-menu.el:
--------------------------------------------------------------------------------
1 | ;;; selection-menu.el --- "generic" menu to choose one string.
2 | ;;;
3 | ;;; Author: Tomi Ollila -- too ät iki piste fi
4 | ;;;
5 | ;;; Contributions (see git log for details):
6 | ;;;
7 | ;;; Mark Walters:
8 | ;;; 2013-12-15: list-based interface with possibility to have multiline
9 | ;;; menu entries and keybindings for those.
10 | ;;;
11 | ;;; License: GPLv2+
12 |
13 | ;; read-key is available in emacs 23.2 & newer...
14 | (if (fboundp 'read-key)
15 | (defalias 'selection-menu--read-key 'read-key)
16 | (defalias 'selection-menu--read-key
17 | (lambda (msg) (aref (read-key-sequence-vector msg) 0))))
18 |
19 | ;; popup.el or company.el could give insight how to "improve"
20 | ;; key reading (and get mouse events into picture, too)
21 | ;; (or maybe mouse events could already be read but how to handle...)
22 |
23 | (defun selection-menu-current-option ()
24 | (get-text-property (point) 'selection-menu-option))
25 |
26 | (defun selection-menu-current-start ()
27 | (get-text-property (point) 'selection-menu-option-start))
28 |
29 | (defun selection-menu-current-end ()
30 | (get-text-property (point) 'selection-menu-option-end))
31 |
32 | (defun selection-menu-adjust ()
33 | (let ((start (selection-menu-current-start)))
34 | (when start
35 | (goto-char start))))
36 |
37 | (defun selection-menu-up ()
38 | (goto-char (selection-menu-current-start))
39 | (unless (bobp)
40 | (forward-line -1)
41 | (selection-menu-adjust)))
42 |
43 | (defun selection-menu-down ()
44 | (let ((current-point (point)))
45 | (goto-char (selection-menu-current-end))
46 | (unless (selection-menu-adjust)
47 | (goto-char current-point))))
48 |
49 | (defun selection-menu--select (ident &optional unread key-short-cut-list)
50 | (let ((helpmsg "Type ESC to abort, Space or Enter to select.")
51 | (buffer-read-only t)
52 | first last overlay pevent select)
53 | (forward-line -1)
54 | (setq last (point))
55 | (goto-char (point-min))
56 | (setq first (point))
57 | (save-window-excursion
58 | (pop-to-buffer (current-buffer))
59 | (setq mode-name "Selection Menu"
60 | mode-line-buffer-identification (concat "*" ident "*"))
61 | (setq overlay (make-overlay (selection-menu-current-start) (selection-menu-current-end)))
62 | (overlay-put overlay 'face 'highlight)
63 | (while
64 | (let ((event (selection-menu--read-key helpmsg)))
65 | (cond ((or (eq event 'up) (eq event 16))
66 | (selection-menu-up)
67 | (move-overlay overlay (selection-menu-current-start) (selection-menu-current-end))
68 | t)
69 | ((or (eq event 'down) (eq event 14))
70 | (selection-menu-down)
71 | (move-overlay overlay (selection-menu-current-start) (selection-menu-current-end))
72 | t)
73 | ((or (eq event 32) (eq event 13) (eq event 'return))
74 | (setq select
75 | (selection-menu-current-option))
76 | nil)
77 | ((setq select (plist-get key-short-cut-list event))
78 | nil)
79 | ((eq event 'escape)
80 | nil)
81 | (t (setq pevent event)
82 | nil)
83 | ))))
84 | (if (and unread pevent)
85 | (push pevent unread-command-events))
86 | (message nil)
87 | select))
88 |
89 | (defun selection-menu (ident items &optional unread)
90 | "Pops up a buffer listing lines given ITEMS one per line.
91 | Use arrow keys (or C-p/C-n) to select and SPC/RET to select.
92 | Return to parent buffer when any other key is pressed.
93 | In this case if optional UNREAD is non-nil return the
94 | read key back to input queue for parent to consume."
95 | (if (eq (length items) 0) nil
96 | (save-excursion
97 | (with-temp-buffer
98 | (let (key-short-cut-list)
99 | (dolist (item items)
100 | (let ((option (if (listp item) (nth 0 item) item))
101 | (description (if (listp item) (nth 1 item) (concat " " item)))
102 | (key-short-cuts (if (listp item) (nth 2 item)))
103 | (start (point-marker))
104 | end)
105 | (insert description "\n")
106 | (setq end (point-marker))
107 | (dolist (key key-short-cuts)
108 | (setq key-short-cut-list (plist-put key-short-cut-list key option)))
109 | (put-text-property start end 'selection-menu-option option)
110 | (put-text-property start end 'selection-menu-option-start start)
111 | (put-text-property start end 'selection-menu-option-end end)))
112 | (selection-menu--select ident unread key-short-cut-list))))))
113 |
114 | ;;(selection-menu "foo" (list))
115 | ;;(selection-menu "foo" (list "a"))
116 | ;;(selection-menu "Send mail to:" (list "a" "b" "c" "d" "faaarao") t)
117 | ;;(selection-menu "Send mail to:" (list '("a" "alpha") '("b" "beta") '("c" "gamma") '("d" "delta")) t)
118 | ;;(selection-menu "Send mail to:" (list (list "alpha" "alpha" '(?a ?A)) (list "bravo" "beta" '(?b ?B)) (list "cat" "gamma" '(?c ?C))) t)
119 | ;; test by entering c-x c-e at the end of previous lines
120 |
121 | (provide 'selection-menu)
122 |
--------------------------------------------------------------------------------
/selection-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/domo141/nottoomuch/729bf5b344de2a757d74fde9f82826f0fcede38c/selection-menu.png
--------------------------------------------------------------------------------
/selection-menu.rst:
--------------------------------------------------------------------------------
1 | selection-menu.el
2 | =================
3 |
4 | `selection-menu.el `_ is an emacs lisp module/function
5 | providing:
6 | ::
7 |
8 | (defun selection-menu (ident items &optional unread)
9 | "Pops up a buffer listing lines given ITEMS one per line.
10 | Use arrow keys (or C-p/C-n) to select and SPC/RET to select.
11 | Return to parent buffer when any other key is pressed.
12 | In this case if optional UNREAD is non-nil return the
13 | read key back to input queue for parent to consume." ...)
14 |
15 | Notmuch-emacs address completion usage
16 | --------------------------------------
17 |
18 | With notmuch 0.16 (or newer) adding the following emacs lisp piece
19 | to your notmuch initialization file will make notmuch address completion
20 | feature use selection menu:
21 | ::
22 |
23 | (require 'selection-menu)
24 | (setq notmuch-address-selection-function
25 | (lambda (prompt collection initial-input)
26 | (selection-menu "Send To:" (cons initial-input collection) t)))
27 |
28 | See also
29 | --------
30 |
31 | `nottoomuch-addresses `_
32 |
33 |
34 | Example picture
35 | ---------------
36 |
37 | In the illustrated example picture below you can see selection-menu in
38 | action when completing email addresses.
39 |
40 | .. image:: selection-menu.png
41 |
--------------------------------------------------------------------------------
/startfetchmail.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- mode: shell-script; sh-basic-offset: 8; tab-width: 8 -*-
3 | # $ startfetchmail.sh $
4 | #
5 | # Created: Wed 06 Mar 2013 17:17:58 EET too
6 | # Last modified: Fri 08 Sep 2017 17:31:02 +0300 too
7 |
8 | # Fetchmail does not offer an option to daemonize it after first authentication
9 | # is successful (and report if it failed). After 2 fragile attempts to capture
10 | # the password in one-shot fetch (using tty play) and if that successful
11 | # run fetchmail in daemon mode I've settled to run this script and just check
12 | # from log whether authentication succeeded...
13 |
14 | set -euf # hint: sh -x thisfile [args] to trace execution
15 |
16 | case ${1-} in -I) idle=; shift ;; *) idle=idle
17 | esac
18 |
19 | case $# in 5) ;; *) exec >&2
20 | echo
21 | echo Usage: $0 [-I] '(143|993|995) (keep|nokeep)' user server mda_cmdline
22 | echo
23 | echo This script runs fetchmail with options to use encrypted IMAP
24 | echo '(or POP3)' connection when fetching email. STARTTLS is required
25 | echo when using port 143. IMAP IDLE feature used when applicable...
26 | echo
27 | echo ... except -I option can be used to inhibit IMAP IDLE usage, in
28 | echo cases where mail delivery is delayed or does not happen with IDLE.
29 | echo
30 | echo fetchmail is run in background '(daemon mode)' and first few
31 | echo seconds of new fetchmail log is printed to terminal so that user
32 | echo can determine whether authentication succeeded.
33 | echo
34 | echo Examples:
35 | echo
36 | echo ' ' $0 993 keep $USER mailhost.example.org \\
37 | echo " '/usr/bin/procmail -d %T'"
38 | echo
39 | echo ' Deliver mail from imap[s] server to user mbox in spool'
40 | echo ' directory (usually /var[/spool]/mail/$USER, or $MAIL).'
41 | echo ' Mails are not removed from imap server.'
42 | echo
43 | echo ' ' $0 143 nokeep $USER mailhost.example.org \\
44 | echo " './nottoomuch/md5mda.sh --cd mail received wip log'"
45 | echo
46 | echo ' Deliver mail from imap server (STARTTLS required) to'
47 | echo ' separate mails in $HOME/mail/received/??/ directories.'
48 | echo ' Mails are removed from imap server.'
49 | echo
50 | exit 1
51 | esac
52 |
53 | proto=IMAP
54 | case $1 in 143) ssl='sslproto TLS1'
55 | ;; 993) ssl=ssl
56 | ;; 995) ssl=ssl idle= proto='POP3 uidl' # uidl there is a bit hacky...
57 | ;; *) exec >&2
58 | echo
59 | echo "$0: '$1' is not '143', '993' nor '995'".
60 | exit 1
61 | echo
62 | esac
63 |
64 | case $2 in keep) keep='keep';; nokeep) keep= ;; *) exec >&2
65 | echo
66 | echo "$0: '$2' is not either 'keep' or 'nokeep'".
67 | echo
68 | exit 1
69 | esac
70 | shift 2
71 |
72 | imap_user=$1 imap_server=$2 mda_cmdline=$3
73 | shift 3
74 | readonly ssl keep imap_server imap_user mda_cmdline
75 |
76 | echo cd "$HOME"
77 | cd "$HOME"
78 |
79 | mda_cmd=`exec expr "$mda_cmdline" : ' *\([^ ]*\)'`
80 | test -s $mda_cmd || {
81 | exec >&2
82 | case $mda_cmd
83 | in ./*) echo "Cannot find command '$HOME/$mda_cmd'"
84 | ;; /*) echo "Cannot find command '$mda_cmd'"
85 | ;; *) echo "Cannot find command '$HOME/$mda_cmd'"
86 | esac
87 | exit 1
88 | }
89 |
90 | if test -f .fetchmail.pid
91 | then
92 | read pid < .fetchmail.pid
93 | if kill -0 "$pid" 2>/dev/null
94 | then
95 | echo "There is (fetchmail) process running in pid $pid"
96 | ps -p "$pid"
97 | echo "If this is not fetchmail, remove the file"
98 | exit 1
99 | fi
100 | fi
101 |
102 | logfile=.fetchmail.log
103 | if test -f $logfile
104 | then
105 | echo "Rotating logfile '$logfile'"
106 | mv "$logfile".2 "$logfile".3 2>/dev/null || :
107 | mv "$logfile".1 "$logfile".2 2>/dev/null || :
108 | mv "$logfile" "$logfile".1
109 | fi
110 | touch $logfile
111 |
112 | tail -f $logfile &
113 | logfilepid=$!
114 |
115 | trap 'rm -f fmconf; kill $logfilepid' 0
116 |
117 | umask 077
118 | echo "
119 | set daemon 120
120 | set logfile '$logfile'
121 |
122 | poll '$imap_server' proto $proto
123 | user '$imap_user' $ssl $keep $idle
124 | mda '$mda_cmdline'
125 | " | tee fmconf
126 |
127 | ( set -x; exec fetchmail -f fmconf -v )
128 |
129 | #x fetchmail -f /dev/null -k -v -p IMAP --ssl --idle -d 120 --logfile $logfile\
130 | # -u USER --mda '/usr/bin/procmail -d %T' SERVER
131 |
132 | sleep 2
133 | test -s $logfile || sleep 2
134 | ol=0
135 | for i in 1 2 3 4 5 6 7 8 9 0
136 | do
137 | nl=`exec stat -c %s $logfile 2>/dev/null` || break
138 | test $nl != $ol || break
139 | ol=$nl
140 | sleep 1
141 | done
142 | rm -f fmconf
143 | trap '' 15 # TERM may not be supported in all shells...
144 | exec 2>/dev/null # be quiet from no on...
145 | kill $logfilepid
146 | trap - 0 15
147 | echo
148 | ps x | grep '\'
149 | echo
150 | echo "Above the end of current fetchmail log '$HOME/$logfile'"
151 | echo "is shown. Check there that your startup was successful."
152 | echo
153 |
--------------------------------------------------------------------------------
/wip/frm-md5mdalog.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Created: Fri Aug 19 16:53:45 2011 +0300 too
4 | # Last Modified: Fri 15 Apr 2016 18:44:06 +0300 too
5 |
6 | # This program examines the log files md5mda.sh has written to
7 | # $HOME/mail/log directory (XXX hardcoded internally to this script)
8 | # and prints the from & subject lines from those.
9 | # The -u (update) option marks all currently viewed files read and
10 | # don't read those again (trusting file name ordering and those files
11 | # not truncating).
12 | # The -v (verbose?) option causes the mail filenames to be printed
13 | # so that those can be e.g. removed ahead-of-time.
14 | #
15 | # ( cd $HOME/bin; ln -s path/to/frm-md5mdalog.pl frm ) may be useful...
16 |
17 | # elegance is not a strong point in this program; hacked on the need basis...
18 | # maybe when the desired set of features is known this will be polished.
19 |
20 | #use 5.014; # for tr///r
21 | use 5.10.1; # for \K
22 | use strict;
23 | use warnings;
24 |
25 | use MIME::Base64 'decode_base64';
26 | use MIME::QuotedPrint 'decode_qp';
27 | use Encode qw/encode_utf8 find_encoding _utf8_on/;
28 |
29 | no warnings 'utf8'; # do not warn on malformed utf8 data in input...
30 |
31 | binmode STDOUT, ':utf8';
32 |
33 | sub usage () { die "Usage: $0 [-uvdfqw] [re...]\n"; }
34 |
35 | my ($updateloc, $filenames, $filesonly, $showdels, $fromnew, $wideout) =
36 | (0, 0, 0, 0, 0, 0);
37 | my $quieter = 0;
38 | if (@ARGV > 0 and ord($ARGV[0]) == ord('-')) {
39 | my $arg = $ARGV[0];
40 | $fromnew = $1 if $arg =~ s/^-\K(\d+)$//;
41 | $showdels = 1 if $arg =~ s/-\w*\Kd//;
42 | $updateloc = 1 if $arg =~ s/-\w*\Ku//;
43 | $filenames = 1 if $arg =~ s/-\w*\Kv//;
44 | $filesonly = 1 if $arg =~ s/-\w*\Kf//;
45 | $quieter = 1 if $arg =~ s/-\w*\Kq//;
46 | $wideout = 1 if $arg =~ s/-\w*\Kw//;
47 | usage unless $arg eq '-';
48 | shift @ARGV;
49 | }
50 |
51 | my @relist = map { qr/$_/i } @ARGV;
52 |
53 | $_ = $0; s|.*/||;
54 |
55 | my $md = $ENV{'HOME'} . '/mail';
56 |
57 | chdir $md or die;
58 |
59 | my ($mdlf, $mio);
60 | if (open I, '<', 'log/md5mda-frmloc') {
61 | my $fl = -s I;
62 | if ($fl > 100) {
63 | seek I, -50, 2 or die $!; # seek-end
64 | }
65 | $mdlf = $_ while ();
66 | if (defined $mdlf) {
67 | $mdlf =~ s/^.*\s+(\S+)\s+(\d+)\s*$/$1/;
68 | $mio = (defined $2)? $2: undef;
69 | }
70 | close I;
71 | }
72 |
73 | my @logfiles;
74 |
75 | if (defined $mio) {
76 | $mdlf = 'log/' . $mdlf;
77 | my @alf = ;
78 | my $last;
79 | while (@alf) {
80 | $last = shift @alf;
81 | @logfiles = ( $last ), last if $last eq $mdlf;
82 | }
83 | push @logfiles, $_ foreach (@alf);
84 |
85 | unless (@logfiles and defined $last) {
86 | $mio = '0';
87 | @logfiles = ( $last );
88 | }
89 | }
90 | else {
91 | # last file, 0 loc.
92 | $mdlf = $_ foreach ();
93 | $mio = '0';
94 | @logfiles = ( $mdlf ) if defined $mdlf;
95 | }
96 |
97 | die "No log files to dig mail files from...\n" unless @logfiles;
98 |
99 | my $frmllnk = readlink 'log/md5mda-frmlast';
100 | my ($frmlast, $frmloff);
101 | if (defined $frmllnk && $frmllnk) {
102 | ($frmlast = $frmllnk) =~ s/^(\d+),(\d+)$/log\/md5mda-$1.log/;
103 | $frmloff = $2 if defined $2;
104 | }
105 | #print $frmlast, ',', $frmloff, "\n";
106 |
107 | my $cols = int(qx{stty -a | sed -n 's/.*columns //; T; s/;.*//p'} + 0);
108 | $cols = 80 unless ($cols); #print "Columns: $cols.\n";
109 | my $fw = int ($cols / 3 - 1); my $sw = int ($cols / 3 * 2 - 1);
110 |
111 | my $hdrline;
112 | sub init_next_hdr()
113 | {
114 | $hdrline = ; $. = 0;
115 | }
116 | sub get_next_hdr()
117 | {
118 | return 0 if $hdrline =~ /^$/;
119 | while () {
120 | chomp $hdrline, $hdrline = $hdrline . $_, next if s/^[ \t]+/ /;
121 | ($_, $hdrline) = ($hdrline, $_);
122 | return 1;
123 | }
124 | $_ = $hdrline; $hdrline = ''; $.++;
125 | return 1;
126 | }
127 |
128 | sub decode_data() {
129 | local $_ = $1;
130 | if (s/^utf-8\?(q|b)\?//i) {
131 | return (lc $1 eq 'q')? (tr/_/ /, decode_qp($_)): decode_base64($_);
132 | }
133 | if (s/^([\w-]+)\?(q|b)\?//i) {
134 | my $t = lc $2;
135 | my $o = find_encoding($1);
136 | if (ref $o) {
137 | my $s = ($t eq 'q')? (tr/_/ /, decode_qp($_)): decode_base64($_);
138 | # Encode(3p) is fuzzy whether encode_utf8 is needed...
139 | return encode_utf8($o->decode($s));
140 | }
141 | }
142 | return "=?$_?=";
143 | }
144 |
145 | my ($mails, $smails, $odate) = (0, 0, '');
146 |
147 | my %lastfns;
148 | sub mailfrm($)
149 | {
150 | my ($sbj, $frm, $dte, $spam);
151 | init_next_hdr;
152 | while (get_next_hdr)
153 | {
154 | $sbj = $1 if (/^Subject:\s*(.*?)\s*$/i);
155 | $frm = $1 if (/^From:\s*(.*?)\s*$/i);
156 | $dte = $1 if (/^Date:\s*(.*?)\s*$/i);
157 | $spam = 1 if /^X-Bogosity:.*\bSpam\b/i;
158 | }
159 |
160 | $dte =~ s/ (\d\d\d\d).*/ $1/; $dte =~ s/\s(0|\s)/ /g;
161 | $odate = $dte, print "*** $dte\n"
162 | if $dte ne $odate and not $filesonly and not $quieter;
163 |
164 | $sbj = "" unless defined $sbj;
165 | #$frm = "" unless defined $frm;
166 | if ($spam) { $smails++; }
167 | else {
168 | # could split to $1, $2 & $3...
169 | $frm =~ s/\?=\s+=\?/\?==\?/g;
170 | $frm =~ s/=\?([^?]+\?.\?.+?)\?=/decode_data/ge;
171 | unless ($filesonly) {
172 | $_ = $_[0]; s|.*/||;
173 | $frm="!$frm" if defined $lastfns{$_};
174 | }
175 | $sbj =~ s/\?=\s+=\?/\?==\?/g;
176 | $sbj =~ s/=\?([^?]+\?.\?.+?)\?=/decode_data/ge;
177 | my $line;
178 | if ($wideout) { $line = $frm . ' ' . $sbj;
179 | } else {
180 | _utf8_on($frm); _utf8_on($sbj); # for print widths...
181 | $line = sprintf '%-*.*s %-.*s', $fw, $fw, $frm, $sw, $sbj;
182 | }
183 | sub rechk ($) { foreach (@relist) { return 0 if ($_[0] =~ $_); } 1; }
184 | unless (@relist and rechk $line) {
185 | print $line, "\n" unless $filesonly;
186 | print " $md/$_[0]\n" if $filenames or $filesonly;
187 | print "\n" if $filenames;
188 | }
189 | }
190 | $mails += 1;
191 | }
192 |
193 | # read filenames of last mails imported, from new-* files, to know whether
194 | # taken by notmuch already (XXX 20140821 is this consistent XXX)
195 |
196 | my @_i = ( 1 );
197 | while () {
198 | $_i[$_i[0]++] = $_;
199 | $_i[0] = 1 if $_i[0] > ($fromnew || 3);
200 | }
201 |
202 | shift @_i;
203 | foreach (@_i) {
204 | open L, '<', $_ or die "$_: $!\n";
205 | while () {
206 | if ($fromnew) {
207 | # XXX so much duplicate w/ loop below
208 | /\s(\/\S+)\s+$/ || next;
209 | open I, '<', "$1" or do {
210 | print " ** $1: deleted **\n" if $showdels; next;
211 | };
212 | my $f = $1;
213 | mailfrm $1;
214 | close I;
215 | }
216 | else {
217 | $lastfns{$1} = 1 if /\/([a-z0-9]{9,})$/;
218 | }
219 | }
220 | close L;
221 | }
222 | undef @_i;
223 | exit if $fromnew;
224 |
225 | my $omio = $mio;
226 | my ($cmio, $ltime);
227 | foreach (@logfiles) {
228 | $mdlf = $_;
229 | print "Opening $mdlf... (offset $mio)\n" unless $filesonly or $quieter;
230 | open L, '<', $_ or die "Cannot open '$mdlf': $!\n";
231 | seek L, $mio, 0 if $mio > 0;
232 |
233 | while ()
234 | {
235 | $mio += length;
236 | if (m|(\w\w\w. \d\d:\d\d).*'(.*)'\s*$|) {
237 | $ltime = $1;
238 | open I, '<', "$2" or do {
239 | print " ** $2: deleted **\n" if $showdels; next;
240 | };
241 | my $f = $2;
242 | mailfrm $f;
243 | close I;
244 | }
245 | }
246 | close L;
247 | $cmio = $mio;
248 | $mio = 0;
249 | }
250 |
251 | if (defined $ltime and ! $filesonly and ! $quieter) {
252 | $ltime =~ tr/)//d;
253 | print "*** Last mail received: $ltime.\n";
254 | }
255 |
256 | if ($cmio != $omio or @logfiles > 1) {
257 |
258 | $mdlf =~ /md5mda-(\d+)/;
259 | my $newlink = "$1,$cmio";
260 | if (not defined $frmllnk or $newlink ne $frmllnk) {
261 | unlink 'log/md5mda-frmlast';
262 | symlink "$1,$cmio", 'log/md5mda-frmlast';
263 | }
264 |
265 | if ($updateloc) {
266 | my @lt = localtime;
267 | my @wds = qw/Sun Mon Tue Wed Thu Fri Sat Sun/;
268 | my $date = sprintf("%d-%02d-%02d (%s) %02d:%02d:%02d",
269 | $lt[5] + 1900, $lt[4] + 1, $lt[3], $wds[$lt[6]],
270 | $lt[2], $lt[1], $lt[0]);
271 | open O, '>>', 'log/md5mda-frmloc';
272 | $mdlf =~ s/log\///;
273 | syswrite O, "$date $mdlf $cmio\n";
274 | print "Offset updated to $mdlf: $cmio\n" unless $filesonly;
275 | close O;
276 | }
277 | elsif (not $filesonly and $showdels and defined $frmloff
278 | and $mdlf eq $frmlast and $frmloff eq $cmio) {
279 | my @l = lstat 'log/md5mda-frmlast';
280 | @l = localtime $l[9];
281 | my @d = qw/Sun Mon Tue Wed Thu Fri Sat Sun/;
282 | my $time = sprintf '%02d:%02d', $l[2], $l[1];
283 | print "*** No new mail since last frm run ($d[$l[6]] $time).\n";
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/wip/mbox-to-mda.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # $Id; mbox-to-mda.sh $
3 | #
4 | # Copyright (c) 2011 Tomi Ollila
5 | # All rights reserved
6 | #
7 | # Created: Tue Jul 26 11:45:58 2011 (+0300) too
8 | # Last modified: Sun 19 Oct 2014 12:03:52 +0300 too
9 |
10 | set -eu
11 |
12 | case ${BASH_VERSION-} in *.*) shopt -s xpg_echo; esac
13 | case ${ZSH_VERSION-} in *.*) setopt shwordsplit; esac
14 |
15 | saved_IFS=$IFS
16 | readonly saved_IFS
17 |
18 | die () { echo "$@" >&2; exit 1; }
19 |
20 | usage () {
21 | bn=`basename "$0"`
22 | echo
23 | echo Usage: $bn [-q] [--movemail to-file] mbox-file mda-cmd [mda-args]
24 | echo
25 | }
26 |
27 | set_argval () { shift; argval="$*"; }
28 |
29 | mmmboxf=
30 | verbose_echo () { echo "$@"; }
31 | while case ${1-} in
32 | -q) verbose_echo () { :; } ;;
33 | -h|-?|--help) usage; exec sed -n '/^This program /,$ p' "$0" ;;
34 | --movemail=*) IFS==; set_argval $1; IFS=$saved_IFS; mmmboxf=$argval ;;
35 | --movemail) mmmboxf=$2; shift ;;
36 | --) shift; false ;;
37 | -|-*) die "'$1': unknown option" ;;
38 | *) false
39 | esac; do shift; done
40 |
41 | case $# in 0|1)
42 | exec >&2
43 | usage
44 | echo Enter '' $0 --help '' for more help
45 | echo
46 | exit 1
47 | esac
48 |
49 | mbox=$1
50 | shift
51 |
52 | test -f "$mbox" || die "'$mbox': no such file"
53 | test -x "$1" || die "'$1' cannot be executed"
54 | test -s "$mbox" || { verbose_echo "Mail file '$mbox' is empty"; exit 0; }
55 |
56 | case $mmmboxf in '') ;; *)
57 | # search for movemail(1)
58 | movemail=`exec which movemail 2>/dev/null` || :
59 | case $movemail in /*/movemail) echo foo ;;
60 | *) movemail=`emacs --batch --eval '(progn (defun find-bin (path)
61 | (if (file-exists-p (concat (car path) "/movemail"))
62 | (message (concat (car path) "/movemail"))
63 | (if (not (eq (cdr path) nil))
64 | (find-bin (cdr path)))))
65 | (find-bin exec-path))' 2>&1 | tail -1 || :`
66 | case $movemail in /*/movemail) ;;
67 | *) die "Cannot find 'movemail' program"
68 | esac
69 | esac
70 |
71 | verbose_echo Using movemail $movemail
72 | esac
73 |
74 | tmmf=
75 | case $mmmboxf in '') ;; *)
76 | tmmf=$mmmboxf.wip
77 |
78 | "$movemail" "$mbox" "$tmmf"
79 |
80 | test -s "$tmmf" || {
81 | verbose_echo "Moved mail file '$tmmf' is empty."
82 | rm -f "$tmmf"
83 | exit 0
84 | }
85 | esac
86 |
87 | # This used to be...
88 | # "$formail" -bz -R 'From ' X-From-Line: -s "$@" < "${tmmf:-$mbox}"
89 | # ... but that just split too eagerly (saw >= 2 headers (w/o From ) - new mail)
90 |
91 | # XXX this is a bit naive, 'From ' w/o some following headers is also accepted
92 | # XXX but it would be considerable more complex to support caching of lines...
93 | perl -e 'my $el = 1; while () { if ($el && s/^From /X-From-Line: /) {
94 | open P, "|-", @ARGV or die "Executing @ARGV: $!\n";
95 | }
96 | $el = (/^$/)? 1: 0;
97 | print P $_;
98 | } close P' "$@" < "${tmmf:-$mbox}"
99 |
100 | case $tmmf in '') ;; *) mv "$tmmf" "$mmmboxf" || :
101 | esac
102 | exit
103 |
104 | # Old thoughts about lockfile(1) usage.. now replaced with movemail(1).
105 |
106 | # If I ctrl-c when lockfile running, It seems to be possible
107 | # that lockfile gets created (but not deleted), if ctrl-c
108 | # hits between creating lock and trap instantiation.
109 | # trap cannot be instantiated earlyer as it would make possible
110 | # removing lock created by another process.
111 | # I wish there were way to determine (random) content in lockfile
112 | # then we could check the contents and if matches, then delete
113 | # the lockfile is content matches.
114 |
115 | # Internally this script uses 'movemail' to move mails from mbox file
116 | # (supposedly atomically) and 'formail' to split these mails to
117 | # separate streams each given to mda-cmd provided in command line...
118 |
119 | This program delivers all mail from mbox file to an MDA program given
120 | on command line.
121 |
122 | Options:
123 | -q keep quiet on non-fatal messages
124 | --movemail to-file move mail file to new location (to avoid
125 | concurrent updates) before continuing
126 |
127 | mbox-file the mbox file where mails are to be extracted
128 | mda-cmd mail delivery agent command which is executed for each
129 | individual email which are present in mbox-file
130 | [mda-args] optional arguments for mda-cmd
131 |
132 | Example: Shell wrapper which uses md5mda.sh as MDA.
133 |
134 | cat > mbox-to-md5mda.sh <&2
16 | die () { printf '%s\n' "$@"; exit 1; } >&2
17 |
18 | usage () { print %s\\n "Usage: $0 $cmd $*"; exit 1; } >&2
19 |
20 | x () { printf '+ %s\n' "$*" >&2; "$@"; }
21 | x_eval () { printf '+ %s\n' "$*" >&2; eval "$*"; }
22 | x_exec () { printf '+ %s\n' "$*" >&2; exec "$@"; die "exec '$*' failed"; }
23 |
24 | yesno ()
25 | {
26 | echo
27 | printf '%s (yes/NO)? ' "$*"
28 | read ans
29 | echo
30 | case $ans in ([yY][eE][sS]) return 0; esac
31 | return 1
32 | } /dev/null` || :
70 | case $_xd0 in /*) d0=$_xd0; esac
71 | esac
72 | }
73 |
74 | cmd_mua () # Launch emacs as mail user agent.
75 | {
76 | case ${1-} in -y) ;; *)
77 | if ps x | grep 'emacs[ ].*[ ]notmuch'
78 | then echo
79 | echo '^^^ notmuch emacs mua already running ? ^^^'
80 | echo
81 | echo "to run another, add '-y' to the command line"
82 | echo
83 | exit 0
84 | fi
85 | esac
86 | if test "${DISPLAY-}"
87 | then set -x
88 | #exec nohup setsid emacs -g 108x38 -f notmuch >/dev/null
89 | exec nohup setsid emacs -f notmuch >/dev/null
90 | # the command above works best on linux interactive terminal
91 | else
92 | exec emacs -f notmuch
93 | fi
94 | }
95 |
96 | mbox2md5mda ()
97 | {
98 | set_d0
99 | tmpfile=`cd $HOME/mail; LC_ALL=C exec perl -e '
100 | mkdir q"wip" unless -d q"wip";
101 | system q"mktemp", (sprintf q"wip/mbox-%x,XXXX", time)'`
102 | x $d0/mbox-to-mda.sh --movemail $HOME/mail/$tmpfile $MAIL \
103 | $d0/md5mda.sh --cd $HOME/mail received wip log ||
104 | : ::: mbox-to-mda.sh exited nonzero ::: :
105 | test -s $HOME/mail/$tmpfile || rm $HOME/mail/$tmpfile || :
106 | }
107 |
108 | cmd_new () # Import new mail.
109 | {
110 | #TIMEFORMAT is used by (bash) shell builtin, TIME by gnu /usr/bin/time..
111 | TIMEFORMAT='%Us user %Ss system %P%% cpu %Rs total'
112 | TIME='%Us user, %Ss system, %P cpu, %E total (max resident mem %Mk)'
113 | export TIME
114 | ymdhms=`exec date +%Y%m%d-%H%M%S`
115 | case ${MAIL-} in '') # nothing to do when ''
116 | ;; *"[$IFS]"*) warn "'$MAIL' contains whitespace. Ignored."
117 | ;; /var/*mail/*) test ! -s $MAIL || mbox2md5mda
118 | ;; *) warn "Suspicious '$MAIL' path. Ignored."
119 | esac
120 | case ${1-} in -) exit; esac
121 | set -x
122 | time notmuch new --verbose | tee -a $HOME/mail/wip/new-$ymdhms.log
123 | read line < $HOME/mail/wip/new-$ymdhms.log
124 | case $line in 'No new mail.'*) rm $HOME/mail/wip/new-$ymdhms.log
125 | ;; *) mv $HOME/mail/wip/new-$ymdhms.log $HOME/mail/log/new-$ymdhms.log
126 | esac
127 | }
128 |
129 | cmd_help () # emulate notmuch help by fetching pages from notmuch wiki
130 | {
131 | test $# = 1 || exec notmuch help
132 | if command -v wget >/dev/null
133 | then fcl='wget -O-'
134 | elif command -v curl >/dev/null
135 | then fcl='curl -L'
136 | else
137 | die 'no wget nor curl available'
138 | fi
139 | set -x
140 | $fcl http://notmuchmail.org/manpages/notmuch-$1-1/ | \
141 | sed -e '1,/id="content"/d' -e 's|<.>||g' -e 's|||g' \
142 | -e 's/]*>//g' -e '/id="footer"/q' | less -s
143 | }
144 |
145 | cmd_frm () # Run frm-md5mdalog.pl.
146 | {
147 | set_d0
148 | case ${1-} in mvto:*) ;; *) exec $d0/frm-md5mdalog.pl "$@" ;; esac
149 | case $# in 1) usage "mvto:path" match-re ;; esac
150 | ddir=${1#*:}
151 | test -d "$ddir" || die "'$ddir': no such directory"
152 | shift
153 | echo
154 | tf=`exec mktemp`; trap "rm -f $tf" 0 INT TERM HUP QUIT
155 | $d0/frm-md5mdalog.pl -qvw "$@" | tee $tf |\
156 | grep -v -e '^ ' -e '^$' || exit 0
157 | yesno "Move the messages listed above to '$ddir'"
158 | mv -fv `exec grep '^ */.*/' $tf` "$ddir"
159 | }
160 |
161 | cmd_startfemmda5 () # startfetchmail.sh using md5mda.sh mda
162 | {
163 | case $# in 4|5) ;; *)
164 | usage '[-I]' '(143|993)' '(keep|nokeep)' user server
165 | esac
166 | set_d0
167 | try_canon_d0
168 | cd $HOME
169 | test -d mail || mkdir mail
170 | case $d0 in $PWD/*) d0=${d0#$PWD/}; esac
171 | set -x
172 | exec $d0/startfetchmail.sh $@ \
173 | "$d0/md5mda.sh --cd mail received wip log"
174 | }
175 |
176 | cmd_delete () # remove emails with tag deleted
177 | {
178 | case $#${1-}
179 | in '1!')
180 | x_eval 'notmuch search --output=files tag:deleted | xargs rm -v'
181 | exit
182 | ;; '1s')
183 | x notmuch search tag:deleted
184 | ;; 0)
185 | x notmuch address tag:deleted
186 | echo "Append '!' to the the end of the command line to do actual deletion."
187 | ;; *)
188 | usage "('!' | 's')"
189 | esac
190 | }
191 |
192 | # --
193 |
194 | case ${1-} in -x) maysetx='set -x'; shift ;; *) maysetx= ;; esac
195 |
196 | bn=${0##*/} # basename
197 |
198 | # if $0 is not 'mm' (is it linked to another name), use it as cmd
199 | case $bn in mm) ;; *) set "$bn" "$@" ;; esac
200 |
201 | case ${1-} in '')
202 | echo
203 | echo Usage: $0 '[-x] [args]'
204 | echo
205 | echo $bn commands available:
206 | echo
207 | sed -n '/^cmd_[a-z0-9_]/ { s/cmd_/ /; s/ () [ #]*/ /
208 | s/$0/'"$bn"'/g; s/\(.\{14\}\) */\1/p; }' $0
209 | echo
210 | echo Commands may be abbreviated until ambiguous.
211 | echo
212 | exit 0
213 | ;; esac
214 |
215 | cm=$1; shift
216 |
217 | #case $cm in
218 | # d) cm=diff ;;
219 | #esac
220 |
221 | cc= cp=
222 | for m in `LC_ALL=C exec sed -n 's/^cmd_\([a-z0-9_]*\) (.*/\1/p' "$0"`
223 | do
224 | case $m in $cm) cp= cc=1 cmd=$cm; break
225 | ;; $cm*) cp=$cc; cc="$m $cc"; cmd=$m
226 | esac
227 | done
228 |
229 | test "$cc" || { echo $0: \'$cm\' -- command not found.; exit 1; }
230 | test ! "$cp" || { echo $0: \'$cm\' -- ambiguous command: matches $cc; exit 1; }
231 | unset cc cp cm
232 | readonly cmd
233 |
234 | #set -x
235 | $maysetx
236 | cmd_$cmd "$@"
237 |
238 |
239 | # Local variables:
240 | # mode: shell-script
241 | # sh-basic-offset: 8
242 | # tab-width: 8
243 | # End:
244 | # vi: set sw=8 ts=8
245 |
--------------------------------------------------------------------------------
/wip/nottoomuch-emacs-mua.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # -*- mode: shell-script; sh-basic-offset: 8; tab-width: 8 -*-
3 |
4 | # Created: Fri 11 Jul 2014 00:12:59 EEST too
5 | # Last Modified: Wed 14 Oct 2015 22:55:05 +0300 too
6 |
7 | set -eu
8 |
9 | # this script uses some bash features. therefore attempt to ensure it is
10 | case ${BASH_VERSION-} in '') echo 'Not BASH!' >&2; exit 1; esac
11 |
12 | if test $# = 0
13 | then
14 | bn=${0##*/}
15 | exec sed -e '1,/^exit/d' -e "s/\$0/$bn/" "$0"
16 | exit not reached
17 | fi
18 |
19 | # escape: "expand" '\' as '\\' and '"' as '\"'
20 | # calling convention: escape -v var "$arg" (like in bash printf).
21 | # printf -v var and ${var//...} are bash features (the only one used...)
22 | escape ()
23 | {
24 | local __escape_arg__=${3//\\/\\\\}
25 | printf -v $2 '%s' "${__escape_arg__//\"/\\\"}"
26 | }
27 |
28 | append_body_text ()
29 | {
30 | bodycode=$bodycode$nl" (insert \"$body\n\")"
31 | body=
32 | }
33 | insert_body_file ()
34 | {
35 | [ -f "$1" ] || { echo "$0: '$1': no such file" >&2; exit 1; }
36 | test "$body" = '' || append_body_text
37 | escape -v arg "$1"
38 | ## XXX yhdistä ?
39 | bodycode=$bodycode$nl" (forward-char (cadr (insert-file-contents \"${arg}\" nil 0 1048576)))"
40 | bodycode=$bodycode$nl" (unless (bolp) (insert \"\\n\"))"
41 | }
42 |
43 |
44 | # note: dollar-single i.e. $'...' is bash (ksh, zsh, mksh) but not dash feature
45 | #nl=$'\n'
46 | nl='
47 | '
48 | exec=exec nw= sep=' '
49 | from= to= subject= cc= bcc= body= bodycode=
50 | var=to
51 | while test $# -gt 0 # note $# does not need quoting
52 | do
53 | arg=$1; shift
54 | case $arg
55 | in -n | --n )
56 | [ "$arg" = "${1-}" ] && shift || {
57 | print_instead () { printf '\n%s\n' "$*"; }
58 | exec=print_instead
59 | continue
60 | }
61 | ;; -nw | --nw )
62 | [ "$arg" = "${1-}" ] && shift || {
63 | nw=-nw
64 | continue
65 | }
66 | ;; -from | --from | [Ff]rom:)
67 | [ "$arg" = "${1-}" ] && shift || {
68 | message_goto='(messge-goto-from)'
69 | [ $# != 0 ] || continue
70 | from=$1; shift
71 | continue
72 | }
73 | ;; -to | -cc | -bcc | --to | --cc | --bcc | [Tt]o: | [Cc]c: | [Bb]cc: )
74 | [ "$arg" = "${1-}" ] && shift || {
75 | var=${arg#-}; var=${var#-}; var=${var%:}
76 | eval "message_goto='(message-goto-$var)'"
77 | [ $# != 0 ] || continue
78 | arg=$1; eval "sep=\"\${$var:+, }\""; shift
79 | }
80 | ;; -subject | --subject | [Ss]ubject: )
81 | [ "$arg" = "${1-}" ] && shift || {
82 | var=subject
83 | eval "message_goto='(message-goto-$var)'"
84 | [ $# != 0 ] || continue
85 | var=subject arg=$1; eval "sep=\"\${$var:+ }\""; shift
86 | }
87 | ;; -file | --file )
88 | [ "$arg" = "${1-}" ] && shift || {
89 | message_goto=
90 | [ $# != 0 ] || continue
91 | insert_body_file "$1"; shift
92 | continue;
93 | }
94 | ;; -text | --text )
95 | [ "$arg" = "${1-}" ] && shift || {
96 | message_goto=
97 | [ $# != 0 ] || continue
98 | var=body arg=$1; eval "sep=\"\${$var:+ }\""; shift
99 | }
100 | ;; -body | --body )
101 | [ "$arg" = "${1-}" ] && shift || {
102 | exec >&2
103 | echo
104 | echo "'$arg' is not supported; use -file or -text instead"
105 | echo
106 | exit 1
107 | }
108 | esac
109 | escape -v arg "$arg"
110 | eval "$var=\${$var:+\$$var$sep}\$arg"
111 | sep=' '
112 | done
113 |
114 | [ "$body" = '' ] || append_body_text
115 |
116 | elisp="\
117 | ${cc:+$nl (message-goto-cc) (insert \"$cc\")}\
118 | ${bcc:+$nl (message-goto-bcc) (insert \"$bcc\")}\
119 | ${bodycode:+$nl (message-goto-body)$bodycode}"
120 |
121 | escape -v _pwd "$PWD"
122 |
123 | exec_mua () { $exec ${EMACS:-emacs} $nw --eval "$@"; }
124 |
125 | elisp_start="prog1 'done (require 'notmuch)"
126 |
127 | if [ "$to" = '' -o "$to" = . ] && [ "$subject" = '' ] && [ "$elisp" = '' ]
128 | then
129 | exec_mua "($elisp_start (notmuch-hello) (cd \"$_pwd\"))"
130 | else
131 | [ "$from" != '' ] && oh="'((From . \"$from\"))" || oh=' nil'
132 |
133 | if [ "$subject" == '' ]
134 | then subject=nil
135 | message_goto=' (message-goto-subject)'
136 | else
137 | subject=\"$subject\"
138 | fi
139 | if [ "$to" == '' ]
140 | then to=nil
141 | message_goto=' (message-goto-to)'
142 | else
143 | to=\"$to\"
144 | fi
145 | exec_mua "(${elisp_start}
146 | ;;(setq message-exit-actions '(save-buffers-kill-terminal))
147 | (notmuch-mua-mail ${to} ${subject}
148 | ${oh} nil (notmuch-mua-get-switch-function))
149 | (cd \"$_pwd\")\
150 | ${elisp}${nl} (set-buffer-modified-p nil)${message_goto})"
151 | fi
152 | exit
153 |
154 | Execute `$0 .` to run just notmuch-hello
155 |
156 | Otherwise enter content on command line: initial active option is '-to'
157 |
158 | The list of options are:
159 | -nw (no window) -n (dry run)
160 | -to -subject -cc -bcc -text -file -from
161 |
162 | In all options -- -prefixed alternatives are also recognized.
163 | In to:, subject:, cc:, bcc:, and from: this format is also accepted.
164 |
165 | Duplicating option (e.g. -to -to) will "escape" it -- produce just
166 | one of it as content of the currently active option.
167 |
168 | If option is last argument on command line it just sets final
169 | cursor position (provided that -to and -subject are set).
170 |
171 | Currently --opt=val is not supported, because it is SMOP. Maybe later...
172 |
173 | Example:
174 | $0 user@example.org -subject give me some -cc -cc
175 | .
176 |
--------------------------------------------------------------------------------
/wip/nottoomuch-gmail-emacs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | :; exec "${EMACS:-emacs}" --debug-init --load "$0" "$@"; exit
4 |
5 | ;; wrapper to set up notmuch emacs MUA, configured emacs smtpmail
6 | ;; to send email via gmail
7 |
8 | ;; you may need https://myaccount.google.com/lesssecureapps
9 | ;; (and possibly https://accounts.google.com/DisplayUnlockCaptcha)
10 | ;; (working alternative not requiring the above would be nice)
11 |
12 | ;; when emacs suggest to save authinfo, press 'e' to edit and remove
13 | ;; password. alternatively, if you know how, save authinfo.gpg
14 |
15 | (require 'smtpmail)
16 |
17 | (eval-after-load "notmuch"
18 | (lambda ()
19 | (setq smtpmail-smtp-server "smtp.gmail.com"
20 | smtpmail-smtp-service 587
21 | smtpmail-stream-type 'starttls
22 | smtpmail-debug-info t
23 | smtpmail-debug-verb t
24 | message-send-mail-function 'message-smtpmail-send-it)))
25 |
26 | (load "notmuch")
27 |
28 | (notmuch-hello)
29 |
30 | ;; Local Variables:
31 | ;; mode: emacs-lisp
32 | ;; End:
33 |
--------------------------------------------------------------------------------
/wip/nottoomuch-wrapper.c:
--------------------------------------------------------------------------------
1 | #if 0 /* -*- mode: c; c-file-style: "stroustrup"; tab-width: 8; -*-
2 | #
3 | # Enter: sh nottoomuch-wrapper.c [/path/to/]notmuch
4 | # in order to compile this program.
5 | # Tip: enter /bin/echo as notmuch command when testing code changes...
6 | set -eu
7 | case ${1-} in '') echo "Usage: sh $0 [/path/to/]notmuch" >&2; exit 1; esac
8 | notmuch=$1; shift; trg=`exec basename "$0" .c`; rm -f "$trg"
9 | WARN="-Wall -Wstrict-prototypes -Winit-self -Wformat=2" # -pedantic
10 | WARN="$WARN -Wcast-align -Wpointer-arith " # -Wfloat-equal #-Werror
11 | WARN="$WARN -Wextra -Wwrite-strings -Wcast-qual -Wshadow" # -Wconversion
12 | WARN="$WARN -Wmissing-include-dirs -Wundef -Wbad-function-cast -Wlogical-op"
13 | WARN="$WARN -Waggregate-return -Wold-style-definition"
14 | WARN="$WARN -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls"
15 | WARN="$WARN -Wnested-externs -Winline -Wvla -Woverlength-strings -Wpadded"
16 | case ${1-} in '') set x -O2; shift; esac
17 | #case ${1-} in '') set x -ggdb; shift; esac
18 | set -x
19 | exec ${CC:-gcc} --std=c99 $WARN "$@" -o "$trg" "$0" -DNOTMUCH="\"$notmuch\""
20 | exit $?
21 | */
22 | #endif
23 | /*
24 | * $ nottoomuch-wrapper.c $
25 | *
26 | * Created: Tue 13 Mar 2012 12:34:26 EET too
27 | * Last modified: Wed 19 May 2021 21:15:19 +0300 too
28 | */
29 |
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 |
38 | #include
39 | #include
40 | #include
41 | #include
42 |
43 | #define null ((void *)0)
44 |
45 | #define WriteCS(f, s) write((f), ("" s ""), sizeof (s) - 1)
46 |
47 | time_t gt;
48 |
49 | static const char * get_time(const char * str, long * timep)
50 | {
51 | unsigned int mult;
52 | char * endptr;
53 | long t;
54 |
55 | long val = strtol(str, &endptr, 10);
56 |
57 | /* no digits at all */
58 | if (endptr == str)
59 | return null;
60 | /* what to do with negative value... nothing */
61 | if (val < 0)
62 | return null;
63 |
64 | switch (*endptr) {
65 | case 's': mult = 1; endptr++; break;
66 | case 'm': mult = 60; endptr++; break;
67 | case 'h': mult = 3600; endptr++; break;
68 | case 'd': mult = 86400; endptr++; break;
69 | case 'w': mult = 7 * 86400; endptr++; break;
70 | case 'M': mult = 30 * 86400; endptr++; break;
71 | case 'y': mult = 365 * 86400; endptr++; break;
72 | case '.':
73 | case ' ':
74 | case '\0':
75 | if (val < 1000) {
76 | mult = 60;
77 | break;
78 | }
79 | *timep = val;
80 | return endptr;
81 | default:
82 | return null;
83 | }
84 |
85 | t = (gt - (mult * val));
86 | if (t < 0)
87 | t = 0;
88 | *timep = t;
89 |
90 | return endptr;
91 | }
92 |
93 | char spcstr[2] = " ";
94 | char nlstr[2] = "\n";
95 |
96 | int main(int argc, char * argv[])
97 | {
98 | char buf[16384];
99 | char * p = buf;
100 | int i, tl = 0;
101 | int fd = 0; /* avoid compiler warning */
102 |
103 | if (argc < 3) {
104 | execvp(NOTMUCH, argv);
105 | /* not reached */
106 | return 1;
107 | }
108 |
109 | gt = time(null);
110 |
111 | #if 0 /* log all commands from now on... */
112 | if (strcmp(argv[1], "tag") == 0 || strcmp(argv[1], "search") == 0 ||
113 | strcmp(argv[1], "show") == 0 || strcmp(argv[1], "reply") == 0)
114 | #endif
115 | {
116 | char * home = getenv("HOME");
117 | if (home) {
118 | sprintf(buf, "%s/nottoomuch-wrapper.log", home);
119 | fd = open(p, O_WRONLY|O_CREAT|O_APPEND, 0644);
120 | if (fd >= 0) {
121 | struct tm * tm = localtime(>);
122 | tl = strftime(buf, 1024, "%Y-%m-%d (%a) %H:%M:%S:", tm);
123 | p += tl;
124 | }
125 | }
126 | }
127 |
128 | /* XXX ugly hack (to do date expansion at 2012-03), got by trial & errror */
129 | for (i = 2; i < argc && argv[i]; i++) {
130 | const char * arg = argv[i];
131 | char * q;
132 | int prefixlen = 0;
133 | char * s = p;
134 |
135 | while ( (q = strstr(arg, "..")) != null) {
136 | /* split into prefix(len), ltd, rtd, postdata */
137 | long st = 0, tt = 0;
138 |
139 | if (q - arg > 0) {
140 | char * r = q - 1;
141 | while (r != arg) {
142 | if (isspace(*r)) {
143 | prefixlen = r - arg + 1;
144 | break;
145 | }
146 | r--;
147 | }
148 | if (r != arg && ! isspace(*r)) {
149 | s = p;
150 | break; /* XXX no conversions on 'error's */
151 | }
152 | if (isspace(*r))
153 | r++;
154 |
155 | if (*r != '.') {
156 | const char * ep = get_time(r, &st);
157 | if (ep != q) {
158 | s = p;
159 | break; /* XXX no conversions on 'error's */
160 | }
161 | }
162 | }
163 | /* right end of .. */
164 | q += 2;
165 | const char *ep;
166 | if (*q == '\0' || isspace(*q))
167 | ep = q;
168 | else {
169 | ep = get_time(q, &tt);
170 | if (ep == null || (*ep != '\0' && ! isspace(*ep))) {
171 | s = p;
172 | break; /* XXX no conversions on 'error's */
173 | }
174 | }
175 |
176 | /* if (buf + 200 - s - prefixlen - 100 < 0) { */
177 | if (buf + sizeof buf - s - prefixlen - 100 < 0) {
178 | s = p;
179 | break; /* XXX data does not fit */
180 | }
181 |
182 | if (prefixlen) {
183 | memcpy(s, arg, prefixlen);
184 | s += prefixlen;
185 | }
186 | prefixlen = 1;
187 | arg = ep;
188 | if (st) {
189 | int l = snprintf(s, 20, "%ld", st);
190 | s += l;
191 | }
192 | *s++ = '.'; *s++ = '.';
193 | if (tt) {
194 | int l = snprintf(s, 20, "%ld", tt);
195 | s += l;
196 | }
197 | }
198 | if (s != p) {
199 | if (*arg) {
200 | int l = strlen(arg);
201 | memcpy(s, arg, l);
202 | s += l;
203 | }
204 | *s++ = '\0';
205 | #if 0
206 | printf("Mangled '%s' to '%s'\n", argv[i], p);
207 | #endif
208 | argv[i] = p;
209 | p = s;
210 | }
211 | }
212 | if (tl > 0) {
213 | struct iovec iov[256];
214 | iov[0].iov_base = buf;
215 | iov[0].iov_len = tl;
216 | for (i = 1; i < 255; i += 2) {
217 | char * s = argv[i/2 + 1];
218 | if (!s)
219 | break;
220 | iov[i].iov_base = spcstr; /* " "; */
221 | iov[i].iov_len = 1;
222 | iov[i+1].iov_base = s;
223 | iov[i+1].iov_len = strlen(s);
224 | }
225 | iov[i].iov_base = nlstr; /* "\n"; */
226 | iov[i++].iov_len = 1;
227 | writev(fd, iov, i);
228 | close(fd);
229 | }
230 | execvp(NOTMUCH, argv);
231 | return 1;
232 | }
233 |
--------------------------------------------------------------------------------