summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2021-04-05 16:42:01 -0700
committerSean Whitton <spwhitton@spwhitton.name>2021-04-05 16:42:01 -0700
commita527e341c147cc6b952ba35f08ce4e116dc200af (patch)
tree9be6e21fc8b304c9aca0560a32aab35ab3fef5bf
parent5e5d198de86e71687677b9312c0ae6fc238b0fbd (diff)
parent7b3d79afdaca217183b9715cfb40979a16cafd89 (diff)
downloadmailscripts-a527e341c147cc6b952ba35f08ce4e116dc200af.tar.gz
Merge tag 'debian/0.23-1' into buster-bpo
mailscripts release 0.23-1 for unstable (sid) [dgit] [dgit distro=debian no-split --quilt=linear] # gpg: Signature made Thu 28 Jan 2021 04:37:17 PM MST # gpg: using RSA key 9B917007AE030E36E4FC248B695B7AE4BF066240 # gpg: Good signature from "Sean Whitton <spwhitton@spwhitton.name>" [ultimate] # Primary key fingerprint: 8DC2 487E 51AB DD90 B5C4 753F 0F56 D055 3B6D 411B # Subkey fingerprint: 9B91 7007 AE03 0E36 E4FC 248B 695B 7AE4 BF06 6240
-rw-r--r--Makefile3
-rw-r--r--debian/changelog21
-rw-r--r--debian/control9
-rw-r--r--debian/copyright2
-rw-r--r--debian/mailscripts.install1
-rw-r--r--debian/mailscripts.manpages1
-rwxr-xr-xgmi2email333
-rw-r--r--gmi2email.1.pod129
-rw-r--r--mailscripts.el67
9 files changed, 549 insertions, 17 deletions
diff --git a/Makefile b/Makefile
index e2ae233..6f52483 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,8 @@ MANPAGES=mdmv.1 mbox2maildir.1 \
imap-dl.1 \
email-extract-openpgp-certs.1 \
email-print-mime-structure.1 \
- notmuch-import-patch.1
+ notmuch-import-patch.1 \
+ gmi2email.1
COMPLETIONS=completions/bash/email-print-mime-structure completions/bash/imap-dl
all: $(MANPAGES) $(COMPLETIONS)
diff --git a/debian/changelog b/debian/changelog
index d7997a1..2be5baa 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,24 @@
+mailscripts (0.23-1) unstable; urgency=medium
+
+ * New script: gmi2email
+ - add libdbd-sqlite3-perl, libio-socket-ssl-perl, libmime-lite-perl,
+ libemail-date-format-perl, libtry-tiny-perl, libmailtools-perl and
+ libxml-feed-perl to Suggests.
+
+ -- Sean Whitton <spwhitton@spwhitton.name> Thu, 28 Jan 2021 16:34:40 -0700
+
+mailscripts (0.22-1) unstable; urgency=medium
+
+ * mailscripts.el:
+ - drop hard dependency on Projectile
+ - add new defcustom, mailscripts-project-library
+ - replace *-projectile commands with new *-to-project commands, which
+ support both Projectile and project.el for choosing from known
+ projects
+ - Use 'cl-case' not 'case' and require cl-lib.
+
+ -- Sean Whitton <spwhitton@spwhitton.name> Tue, 19 Jan 2021 16:14:45 -0700
+
mailscripts (0.21-1~bpo10+1) buster-backports; urgency=medium
* Rebuild for buster-backports.
diff --git a/debian/control b/debian/control
index 4dc17f7..1689e5c 100644
--- a/debian/control
+++ b/debian/control
@@ -60,6 +60,13 @@ Suggests:
gpg-agent,
gpgsm,
openssl,
+ libdbd-sqlite3-perl,
+ libio-socket-ssl-perl,
+ libmime-lite-perl,
+ libemail-date-format-perl,
+ libtry-tiny-perl,
+ libmailtools-perl,
+ libxml-feed-perl,
Architecture: all
Description: collection of scripts for manipulating e-mail on Debian
This package provides a collection of scripts for manipulating e-mail
@@ -84,3 +91,5 @@ Description: collection of scripts for manipulating e-mail on Debian
email-extract-openpgp-certs -- extract OpenPGP certificates from a message
.
imap-dl -- download messages from an IMAP mailbox to a maildir
+ .
+ gmi2email -- subscribe to gemlogs and read individual Gemini pages by e-mail
diff --git a/debian/copyright b/debian/copyright
index db97f3d..1d246a9 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,7 +1,7 @@
mailscripts
Collection of scripts for manipulating e-mail on Debian
-Copyright (C)2017-2020 Sean Whitton
+Copyright (C)2017-2021 Sean Whitton
Copyright (C)2019-2020 Daniel Kahn Gillmor
Copyright (C)2020 Red Hat, Inc.
diff --git a/debian/mailscripts.install b/debian/mailscripts.install
index df220b3..53665b3 100644
--- a/debian/mailscripts.install
+++ b/debian/mailscripts.install
@@ -8,3 +8,4 @@ mbox-extract-patch /usr/bin
notmuch-extract-patch /usr/bin
notmuch-import-patch /usr/bin
notmuch-slurp-debbug /usr/bin
+gmi2email /usr/bin
diff --git a/debian/mailscripts.manpages b/debian/mailscripts.manpages
index 345053a..d704a87 100644
--- a/debian/mailscripts.manpages
+++ b/debian/mailscripts.manpages
@@ -8,3 +8,4 @@ mbox-extract-patch.1
notmuch-extract-patch.1
notmuch-import-patch.1
notmuch-slurp-debbug.1
+gmi2email.1
diff --git a/gmi2email b/gmi2email
new file mode 100755
index 0000000..cbbded4
--- /dev/null
+++ b/gmi2email
@@ -0,0 +1,333 @@
+#!/usr/bin/perl
+
+# gmi2email -- subscribe to gemlogs and read individual Gemini pages by e-mail
+
+# Copyright (C) 2021 Sean Whitton
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# TESTING/DEVEL
+#
+# To forget about seen entries of a feed:
+#
+# % perl -MDBI
+# -we'DBI->connect("dbi:SQLite:dbname=$ENV{HOME}/.cache/mailscripts/gmi2email.db",
+# "", "")->do("DELETE FROM seen WHERE uri LIKE \"gemini://example.com/%\"")'
+
+use 5.028;
+use strict;
+use warnings;
+
+use DBI;
+use File::Spec::Functions "catfile";
+use IO::Socket::SSL;
+use MIME::Lite;
+use Config::Tiny;
+use Text::Wrap;
+use Try::Tiny;
+use Getopt::Long;
+use Email::Date::Format "email_date";
+use Time::Local;
+use Mail::Field::AddrList;
+use XML::Feed;
+
+my ($from, $to, $subs, $inline_images, $no_mail);
+GetOptions
+ "from=s" => \$from,
+ "to=s" => \$to,
+ "subscriptions:s" => \$subs,
+ "inline-images!" => \$inline_images,
+ "no-send!" => \$no_mail;
+
+my $conf_r = $ENV{XDG_CONFIG_HOME} || catfile $ENV{HOME}, ".config";
+my $conf_f = catfile $conf_r, "mailscripts", "gmi2email.config";
+-e $conf_f
+ or (defined $to and defined $from)
+ or die
+ "no config file nor sufficient command line options: don't know who to mail";
+my $conf = Config::Tiny->new->read($conf_f);
+$subs ||= catfile $conf_r, "mailscripts", "gmi2email.subscriptions"
+ if defined $subs;
+
+my %to_mail_opts = (
+ from => (
+ $from
+ or $conf->{_}->{from}
+ or die "no From: address set in config or on command line"
+ ),
+ to => (
+ $to
+ or $conf->{_}->{to}
+ or die "no To: address set in config or on command line"
+ ),
+ inline_images => $inline_images // $conf->{_}->{inline_images} // 0
+);
+
+@ARGV or $subs or die "nothing to do\n";
+
+for (@ARGV) {
+ my $data;
+ if (-f) {
+ open my $fh, "<", $_;
+ $data = [<$fh>];
+ } else {
+ my $type;
+ ($type, $data) = gemini_fetch($_, abs_links => 1);
+ $type =~ m{^text/gemini} or die "$_ is not gemtext";
+ }
+ $no_mail or gemtext_to_mail($data, %to_mail_opts)->send;
+}
+
+exit unless $subs;
+-r $subs or die "file $subs not readable";
+open my $subs_fh, "<", $subs;
+
+my $db_r = $ENV{XDG_CACHE_HOME} || catfile $ENV{HOME}, ".cache";
+my $db_d = catfile $db_r, "mailscripts";
+-d $db_d or mkdir $db_d;
+my $db_f = catfile $db_d, "gmi2email.db";
+my $dbh = DBI->connect("dbi:SQLite:dbname=$db_f", "", "");
+$dbh->do("CREATE TABLE IF NOT EXISTS seen (uri TEXT PRIMARY KEY)")
+ or die "failed to initialise database";
+
+foreach my $sub (<$subs_fh>) {
+ chomp $sub;
+ my ($gemlog, $type, $data, $next);
+ #<<<
+ try {
+ ($type, $data) = gemini_fetch($sub, abs_links => 1);
+ } catch {
+ my ($code) = /"gemini error: ([1-6])/;
+ if ( defined $code and $code == 4
+ or /missing or invalid gemini response/
+ or /failed to establish SSL connection/) {
+ warn "temporary failure retrieving $sub; will try again later:\n $_";
+ $next = 1, return; # try again next run
+ } else {
+ die "while retrieving $sub $_";
+ }
+ };
+ #>>>
+ next if $next;
+ if ($type =~ m{^text/gemini}) {
+ for (@$data) {
+ if (/^#\s*/ and not $gemlog) {
+ $gemlog = $';
+ } elsif (my ($uri, $y, $m, $d, $title)
+ = /^=>\s*(\S+)\s+([0-9]{4})-([0-9]{2})-([0-9]{2})[\s-]*(.*)/) {
+ send_subscribed_gemtext($uri, $gemlog // "unknown gemlog",
+ $title, timelocal 0, 0, 12, $d, $m - 1, $y);
+ }
+ }
+ } elsif ($type =~ m{^(?:text|application)/(?:(?:atom|rss)\+)?xml}) {
+ my $feed = XML::Feed->parse(\$data);
+ for ($feed->entries) {
+ my $date = $_->issued // $_->modified;
+ $date = $date->epoch if $date;
+
+ my $link;
+ if ($_->link =~ m{^//}) {
+ $link = "gemini:" . $_->link;
+ } elsif ($_->link !~ m{^[a-z]+://}) {
+ $link = "gemini://" . $_->link;
+ } else {
+ $link = $_->link;
+ }
+
+ send_subscribed_gemtext($link, $feed->title, $_->title, $date);
+ }
+ } else {
+ die "$sub is not gemtext nor an Atom feed, so far as I can tell";
+ }
+}
+
+sub send_subscribed_gemtext {
+ my ($uri, $gemlog, $link_title, $feed_date) = @_;
+ my ($rows)
+ = $dbh->selectrow_array(
+ "SELECT COUNT(*) FROM seen WHERE uri = \"$uri\"");
+ return unless $rows == 0;
+ my $mail = 1;
+ my ($type, $data);
+ #<<<
+ try {
+ ($type, $data) = gemini_fetch($uri, abs_links => 1);
+ } catch {
+ warn "when fetching $uri, $_";
+ my ($code) = /"gemini error: ([1-6])/;
+ if ($code and $code == 4) {
+ return; # try again next run
+ } else {
+ $mail = 0; # don't try this one again
+ }
+ };
+ #>>>
+ if ($type and $type =~ m{^text/gemini}) {
+ gemtext_to_mail(
+ $data, %to_mail_opts,
+ gemlog => $gemlog // "unknown gemlog",
+ link_title => $link_title,
+ date => email_date $feed_date // time
+ )->send
+ if $mail and !$no_mail;
+ } else {
+ warn "$uri is not gemtext";
+ }
+ $dbh->do("INSERT INTO seen VALUES (\"$uri\")");
+}
+
+sub gemini_fetch {
+ my ($uri, %opts) = @_;
+
+ # regexp from Alex Schroeder's moku-pona program
+ my ($scheme, $authority, $path, $query, $fragment)
+ = $uri
+ =~ m|(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|;
+ $scheme and $scheme eq "gemini"
+ or die "'$uri' does not use the gemini scheme";
+ $authority or die "'$uri' lacks an authority";
+ my ($host, $port) = split ":", $authority;
+ my $cl = IO::Socket::SSL->new(
+ PeerHost => $host,
+ PeerPort => $port // 1965,
+ SSL_verify_mode => SSL_VERIFY_NONE
+ ) or die "while fetching $uri: failed to establish SSL connection\n";
+ print $cl "$uri\r\n";
+
+ my ($status, $meta) = <$cl> =~ /^([0-9]+) (\V+)/;
+ defined $status and defined $meta
+ or die "while fetching $uri: missing or invalid gemini response\n";
+ if (30 <= $status and $status < 40) {
+ $opts{orig_uri} = $uri unless $opts{redirects};
+ die "too many redirects while fetching $opts{orig_uri}"
+ if $opts{redirects} and $opts{redirects} > 5;
+ $opts{redirects}++;
+ return gemini_fetch($meta, %opts);
+ } elsif ($status < 20 or $status >= 40) {
+ die "while fetching $uri: gemini error: $status $meta";
+ }
+
+ if ($meta =~ "^text/gemini") {
+ my @lines;
+ if ($opts{abs_links}) {
+ my $dir = $path =~ s{[^/]*$}{}r =~ s{^/}{}r;
+ $authority =~ m{/$} or $authority .= "/";
+ while (local $_ = <$cl>) {
+ s/\r?\n\z//;
+ if (m{^=>\s*\./} || m{^=>\s*(?!/)} and not m{^=> [a-z]+://}) {
+ my $link = "$dir$'";
+ # attempt to resolve any use of '..' notation
+ 1 while $link =~ s{/[^/]+/../}{/};
+ push @lines, "=> gemini://$authority$link";
+ } elsif (m{^=>\s*/}) {
+ push @lines, "=> gemini://$authority$'";
+ } else {
+ push @lines, $_;
+ }
+ }
+ } else {
+ @lines = <$cl>;
+ }
+ push @lines, "" unless !@lines or $lines[$#lines] eq "";
+ push @lines, "Retrieved from $uri\n at " . localtime;
+ return $meta, \@lines;
+ } else {
+ return $meta, do { local $/; <$cl> };
+ }
+}
+
+sub gemtext_to_mail {
+ my ($gemtext, %opts) = @_;
+ $opts{from} or die "no From: address specified";
+ $opts{to} or die "no To: address specified";
+
+ my $subject = $opts{link_title} // "";
+ if ($gemtext->[0] =~ m{^#(?!#)\s*}) {
+ $subject = $';
+ shift @$gemtext;
+ shift @$gemtext while $gemtext->[0] =~ /^$/;
+ }
+
+ if ($opts{gemlog}) {
+ $opts{from}
+ = Mail::Field->new("From")->create($opts{from}, $opts{gemlog})
+ ->stringify;
+ $subject = "$opts{gemlog}: $subject" if $subject;
+ }
+
+ my $msg = MIME::Lite->new(
+ From => $opts{from},
+ To => $opts{to},
+ Subject => $subject,
+ Type => "multipart/mixed"
+ );
+ $msg->add(Date => $opts{date}) if $opts{date};
+
+ my ($pre, @buffer);
+ my $flush = sub {
+ return unless @buffer;
+ $msg->attach(Type => "TEXT", Data => join "\r\n", @buffer);
+ undef @buffer;
+ };
+ my $pad
+ = sub { push @buffer, "" unless !@buffer or $buffer[$#buffer] eq "" };
+ for (@$gemtext) {
+ if ($pre) {
+ if (/^```/) {
+ $pre = 0;
+ } else {
+ push @buffer, " $_";
+ }
+ } elsif (/^```/) {
+ &$pad;
+ $pre = 1;
+ } elsif (/^>\s*/) {
+ &$pad;
+ push @buffer, split "\n", wrap "> ", "> ", $';
+ } elsif (/^\*\s*/) {
+ &$pad;
+ push @buffer, split "\n", wrap "• ", " ", $';
+ } elsif ($opts{inline_images}
+ and my ($uri) = m{^=>\s*(gemini://\S+\.(?:jpg|jpeg|png|gif))}) {
+ &$flush;
+ my ($type, $data, $failed);
+ #<<<
+ try {
+ ($type, $data) = gemini_fetch($uri);
+ } catch {
+ push @buffer, "when fetching $uri, $_";
+ $failed = 1;
+ };
+ #>>>
+ $msg->attach(
+ Type => $type,
+ Data => $data,
+ Filename => (split "/", $uri)[-1],
+ Disposition => "inline"
+ ) unless $failed;
+ } elsif (/^=>/) {
+ &$pad unless @buffer and $buffer[$#buffer] =~ /^=>/;
+ push @buffer, $_;
+ } elsif (/^#+/) {
+ &$pad;
+ push @buffer, $_;
+ } else {
+ &$pad;
+ push @buffer, split "\n", wrap "", "", $_;
+ }
+ }
+
+ &$flush;
+ return $msg;
+}
diff --git a/gmi2email.1.pod b/gmi2email.1.pod
new file mode 100644
index 0000000..b7e9108
--- /dev/null
+++ b/gmi2email.1.pod
@@ -0,0 +1,129 @@
+=head1 NAME
+
+gmi2email - subscribe to gemlogs and read individual Gemini pages by e-mail
+
+=head1 SYNOPSIS
+
+B<gmi2email> [I<OPTIONS>] [I<URI or FILE>] ...
+
+=head1 DESCRIPTION
+
+B<gmi2email> fetches pages served using the Gemini protocol, converts them to
+e-mail messages, and then sends those messages. It is mainly useful for
+subscribing to Gemini logs ("gemlogs") by e-mail, like rss2email(1).
+B<gmi2email> fetches, converts and sends all URIs and files containing
+text/gemini content specified on the command line.
+
+=head2 TYPICAL USAGE
+
+1. Ensure you have a working MTA: B<gmi2email> will use the sendmail(1)
+command to send mail.
+
+2. Create B<~/.config/mailscripts/gmi2email.config> with content like this:
+
+=over 4
+
+ from = rss@example.com
+ to = your_email@example.com
+ inline_images = 1
+
+=back
+
+3. Create B<~/.config/mailscripts/gmi2email.subscriptions> with some feed
+URIs, e.g.
+
+=over 4
+
+ gemini://example.com/my_cool_gemlog/
+ gemini://example.com/other_cool_gemlog/feed.xml
+
+=back
+
+4. Just once, execute
+
+=over 4
+
+ % gmi2email --subscriptions --no-send
+
+=back
+
+5. Periodically, execute
+
+=over 4
+
+ % gmi2email --subscriptions
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--subscriptions>[=I<FILE>]
+
+In addition to mailing any URIs/files specified on the command line, check
+subscribed gemlogs for new posts and send those too. Useful in a crontab.
+
+We support the subscription mechanism described at
+<gemini://gemini.circumlunar.space/docs/companion/subscription.gmi> as well as
+Atom feeds.
+
+B<gmi2email> looks for a file with a list of gemini:// URIs to check for new
+posts, one per line, in I<FILE>, or if that is not set, in
+B<$XDG_CONFIG_HOME/mailscripts/gmi2email.subscriptions>, or if XDG_CONFIG_HOME
+is not set, it falls back to trying to read
+B<~/.config/mailscripts/gmi2email.subscriptions>.
+
+=item B<--inline-images>
+
+Download and inline any images included in the post.
+
+=item B<--no-send>
+
+Don't actually send any mail. Intended when you just added some new
+subscriptions and want to avoid receiving all the old posts you've already
+read.
+
+=item B<--from=>I<ADDRESS>
+
+Set the From: address, overriding the configuration file.
+
+=item B<--to=>I<ADDRESS>
+
+Set the To: address, overriding the configuration file.
+
+=back
+
+=head1 CONFIGURATION
+
+B<gmi2email> tries to read configuration from the file
+B<$XDG_CONFIG_HOME/mailscripts/gmi2email.config>, or if XDG_CONFIG_HOME is not
+set, it falls back to trying to read
+B<~/.config/mailscripts/gmi2email.config>.
+
+The format is I<key = value>, one per line. The following
+configuration keys are supported:
+
+=over 4
+
+=item B<from>
+
+Set the From: address.
+
+=item B<to>
+
+Set the To: address.
+
+=item inline_images
+
+Set to 1 to implicitly pass B<--inline-images>.
+
+=back
+
+=head1 SEE ALSO
+
+<https://gemini.circumlunar.space/>
+
+=head1 AUTHOR
+
+B<gmi2email> was written by Sean Whitton <spwhitton@spwhitton.name>.
diff --git a/mailscripts.el b/mailscripts.el
index 50e3b89..8dc5875 100644
--- a/mailscripts.el
+++ b/mailscripts.el
@@ -1,8 +1,8 @@
;;; mailscripts.el --- functions to access tools in the mailscripts package
;; Author: Sean Whitton <spwhitton@spwhitton.name>
-;; Version: 0.21
-;; Package-Requires: (notmuch projectile)
+;; Version: 0.23
+;; Package-Requires: (notmuch)
;; Copyright (C) 2018, 2019, 2020 Sean Whitton
@@ -21,8 +21,8 @@
;;; Code:
+(require 'cl-lib)
(require 'notmuch)
-(require 'projectile)
(require 'thingatpt)
(defgroup mailscripts nil
@@ -46,6 +46,23 @@ Note that this does not prevent the creation of new branches."
:type 'boolean
:group 'mailscripts)
+(defcustom mailscripts-project-library 'projectile
+ "Which project management library to use to choose from known projects.
+
+Some mailscripts functions allow selecting the repository to
+which patches will be applied from the list of projects already
+known to Emacs. There is more than one popular library for
+maintaining a list of known projects, however, so this variable
+must be set to the one you use.
+
+Once there is a more fully-featured version of project.el
+included in the latest stable release of GNU Emacs, the default
+value of this variable may change, so if you wish to continue
+using Projectile, you should explicitly customize this."
+ :type '(choice (const :tag "project.el" project)
+ (const :tag "Projectile" projectile))
+ :group 'mailscripts)
+
;;;###autoload
(defun notmuch-slurp-debbug (bug &optional no-open)
"Slurp Debian bug with bug number BUG and open the thread in notmuch.
@@ -123,10 +140,16 @@ threads to the notmuch-extract-patch(1) command."
"*notmuch-apply-thread-series*")))
;;;###autoload
-(defun notmuch-extract-thread-patches-projectile ()
- "Like `notmuch-extract-thread-patches', but use projectile to choose the repo."
+(define-obsolete-function-alias
+ 'notmuch-extract-thread-patches-projectile
+ 'notmuch-extract-thread-patches-to-project
+ "mailscripts 0.22")
+
+;;;###autoload
+(defun notmuch-extract-thread-patches-to-project ()
+ "Like `notmuch-extract-thread-patches', but choose repo from known projects."
(interactive)
- (mailscripts--projectile-repo-and-branch
+ (mailscripts--project-repo-and-branch
'notmuch-extract-thread-patches
(when current-prefix-arg
(prefix-numeric-value current-prefix-arg))))
@@ -157,10 +180,16 @@ git-format-patch(1)."
mm-handle))))
;;;###autoload
-(defun notmuch-extract-message-patches-projectile ()
- "Like `notmuch-extract-message-patches', but use projectile to choose the repo."
+(define-obsolete-function-alias
+ 'notmuch-extract-message-patches-projectile
+ 'notmuch-extract-message-patches-to-project
+ "mailscripts 0.22")
+
+;;;###autoload
+(defun notmuch-extract-message-patches-to-project ()
+ "Like `notmuch-extract-message-patches', but choose repo from known projects."
(interactive)
- (mailscripts--projectile-repo-and-branch 'notmuch-extract-message-patches))
+ (mailscripts--project-repo-and-branch 'notmuch-extract-message-patches))
(defun mailscripts--check-out-branch (branch)
(if (string= branch "")
@@ -173,12 +202,20 @@ git-format-patch(1)."
(concat mailscripts-extract-patches-branch-prefix branch)
branch))))))
-(defun mailscripts--projectile-repo-and-branch (f &rest args)
- (let ((repo (projectile-completing-read
- "Select projectile project: " projectile-known-projects))
- (branch (completing-read
- "Branch name (or leave blank to apply to current HEAD): "
- nil)))
+(defun mailscripts--project-repo-and-branch (f &rest args)
+ (let ((repo (cl-case mailscripts-project-library
+ ('project
+ (require 'project)
+ (project-prompt-project-dir))
+ ('projectile
+ (require 'projectile)
+ (projectile-completing-read
+ "Select Projectile project: " projectile-known-projects))
+ (nil
+ (user-error
+ "Please customize variable `mailscripts-project-library'."))))
+ (branch (read-from-minibuffer
+ "Branch name (or leave blank to apply to current HEAD): ")))
(apply f repo branch args)))
(provide 'mailscripts)