commit 3c86e873219b9ae22b432337506e58a915d0b935
parent afb06bf75f3273012f06c7f23c8eb6542227f677
Author: Jaromil <jaromil@dyne.org>
Date: Mon, 16 Jun 2014 17:16:18 +0200
new feed and html publishing feature (using webnomad)
Diffstat:
6 files changed, 320 insertions(+), 11 deletions(-)
diff --git a/src/jaro b/src/jaro
@@ -20,7 +20,7 @@
# this source code; if not, write to:
# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-VERSION=2.0
+VERSION=2.1
DATE=May/2014
JAROMAILEXEC=$0
typeset -a OLDARGS
@@ -571,6 +571,8 @@ main()
subcommands_opts[init]=""
+ subcommands_opts[publish]=""
+
### Detect subcommand
local -aU every_opts #every_opts behave like a set; that is, an array with unique elements
for optspec in $subcommands_opts$main_opts; do
@@ -718,6 +720,13 @@ main()
isonline) is_online ${=PARAM}; exitcode=$? ;;
+ publish)
+ md="$1"
+ { maildircheck "$md" 2>/dev/null } || { md="${MAILDIRS}/${md}" }
+ pubdb_update "$md"
+ pubdb_render_maildir "$md"
+ exitcode=$? ;;
+
'source') CLEANEXIT=0; return 0 ;;
__default) func "no command provided"
autostart ${PARAM}
diff --git a/src/zlibs/addressbook b/src/zlibs/addressbook
@@ -64,7 +64,7 @@ EOF
func "address already present in $list"
return 1
}
- return $0
+ return 0
}
# update_name() {
diff --git a/src/zlibs/filters b/src/zlibs/filters
@@ -76,15 +76,6 @@ init_inbox() {
return 0
}
-# short utility to print only mail headers
-hdr() {
- { test -r "$1" } || {
- error "hdr() called on non existing file: $1"
- return 1 }
- awk '{ print $0 }
-/^$/ { exit }' "$1"
-}
-
# reads all configurations and creates a cache of what is read
# the cache consists of array and maps declarations for zsh
update_filters() {
diff --git a/src/zlibs/helpers b/src/zlibs/helpers
@@ -78,6 +78,30 @@ autostart() {
}
+# short utility to print only mail headers
+hdr() {
+ { test -r "$1" } || {
+ error "hdr() called on non existing file: $1"
+ return 1 }
+ awk '{ print $0 }
+/^$/ { exit }' "$1"
+}
+
+# short utility to print only mail body
+body() {
+ { test -r "$1" } || {
+ error "body() called on non existing file: $1"
+ return 1 }
+ awk '
+BEGIN { head=1 }
+/^$/ { head=0 }
+{ if(head==0)
+ print $0
+ else
+ next }' "$1"
+}
+
+
#########
## Editor
# this part guesses what is the best editor already present on the system
diff --git a/src/zlibs/maildirs b/src/zlibs/maildirs
@@ -192,6 +192,10 @@ merge() {
notice "Merging maildir ${src} into ${dst}"
c=0
fr=`${=find} ${src}/cur -type f`
+
+ # TODO: preserve timestamps using cp hardlinks
+ # cp -p -r -l source/date target/
+ # rm -rf source/data
for i in ${(f)fr}; do
mv "$i" "${dst}/cur/"; c=$(($c + 1))
{ test $? = 0 } || {
diff --git a/src/zlibs/publish b/src/zlibs/publish
@@ -0,0 +1,281 @@
+#!/usr/bin/env zsh
+#
+# Jaro Mail, your humble and faithful electronic postman
+#
+# a tool to easily and privately handle your e-mail communication
+#
+# Copyleft (C) 2010-2014 Denis Roio <jaromil@dyne.org>
+#
+# This source code is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This source code 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.
+# Please refer to the GNU Public License for more details.
+#
+# You should have received a copy of the GNU Public License along with
+# this source code; if not, write to:
+# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+md=""
+uid=""
+pub=""
+pubdb=""
+
+# creates the database keeping up-to-date information
+# on which emails inside the maildir are already published
+pubdb_create() {
+ func "create PubDB"
+ { test -r "$1" } && {
+ error "PubDBalready exists: $1"
+ return 1
+ }
+ cat <<EOF | ${SQL} -batch "$1"
+CREATE TABLE published
+(
+ uid text collate nocase unique,
+ file text collate nocase,
+ path text collate nocase,
+ date timestamp
+);
+EOF
+ { test $? != 0 } && {
+ error "Error creating PubDB in $1"
+ return 1 }
+ # make sure is private
+ chmod 600 "$1"
+ chown $_uid:$_gid "$1"
+
+ return 0
+}
+
+pubdb_lookup_uid() {
+ func "lookup uid from PubDB: $1"
+ cat <<EOF | ${SQL} -column -batch "$pubdb"
+SELECT file FROM published
+WHERE uid IS "${(Q)1}";
+EOF
+}
+
+pubdb_insert() {
+ _path="$1"
+ _file=`basename "$_path"`
+ # TODO:
+ # _time=`${WORKDIR}/bin/fetchdate "$1" "%Y-%m-%d-%H-%M-%S"`
+ func "insert mail in pubdb: $_path"
+ cat <<EOF | ${SQL} -batch "$pubdb"
+INSERT INTO published (uid, file, path)
+VALUES ("${uid}", "${_file}", "${_path}");
+EOF
+ { test $? != 0 } && {
+ error "insert_mail: duplicate insert in $pubdb"
+ return 1
+ }
+ return 0
+}
+
+pubdb_list() {
+ func "pubdb_list() $1"
+ cat <<EOF | ${SQL} -batch "$pubdb"
+.width 64 128
+SELECT path FROM published
+ORDER BY file DESC;
+EOF
+}
+
+pubdb_getuid() {
+ func "pubdb_getuid() $@"
+ _path="$1"
+ { test -r "$_path" } || { error "path not found for getuid: $_path"; return 1 }
+ uid=`hdr "$_path" | awk '/^Message-ID:/ { gsub(/<|>|,/ , "" , $2); print $2 }'`
+ uid="${(Q)uid%\\n*}" # trim
+ { test "$uid" = "" } && { uid=`basename "$_path"` }
+}
+
+pubdb_update() {
+ func "pubdb_update() $@"
+ md="$1"
+ { test "$md" = "" } && {
+ error "Pubdb_update: maildir not found: $md"
+ return 1 }
+ { maildircheck "${md}" } || {
+ error "Pubdb_update: not a maildir: $md"
+ return 1 }
+ pub="${md}/pub"; ${=mkdir} "$pub"
+ pubdb="${md}/.pubdb"
+ { test -r "$pubdb" } || { pubdb_create "$pubdb" }
+
+ # iterate through emails in maildir and check if new
+ mails=`${=find} "${md}/new" "${md}/cur" "${md}/tmp" -type f`
+ notice "Publishing emails in $md"
+ for m in ${(f)mails}; do
+ pubdb_getuid "$m"
+ u=`pubdb_lookup_uid "$uid"`
+ { test "$u" = "" } && {
+ # $u is a new message
+ func "publish_rss: new message found with uid: $uid"
+ pubdb_insert "$m"
+ }
+ done
+ return 0
+}
+
+pubdb_escape_html() {
+ sed -e '
+s/\&/\&/g
+s/>/\>/g
+s/</\</g
+s/"/\"/g
+'
+}
+
+pubdb_extract_body() {
+ func "pubdb_extract_body() $@"
+ _path="$1"
+ { test -r "$_path" } || { error "mail not found for body extraction: $_path"; return 1 }
+
+ pushd ${TMPDIR}/pubdb
+
+ # check if it has already html
+ _html=`mu extract "$_path" | awk '/text\/html/ {print $1; exit}'`
+ { test "$_html" = "" } || {
+ mu extract --overwrite --parts="$_html" "$_path"
+ awk '
+BEGIN { body=0 }
+/<body/ { body=1; next }
+/<\/body/ { body=0; next }
+{ if(body==1) print $0 }' "$_html".part | iconv -c
+ rm "$_html".part
+ return 0 }
+
+ # use the first text/plain
+ _text=`mu extract "$_path" | awk '/text\/plain/ {print $1; exit}'`
+ { test "$_text" = "" } || {
+ mu extract --overwrite --parts="$_text" "$_path"
+ cat "$_text".part | iconv -c | maruku --html-frag | sed '
+s|http://[^ |<]*|<a href="&">&</a>|g
+s|www\.[^ |<]*|<a href="&">&</a>|g'
+ rm "$_text".part
+ return 0
+ }
+
+ # check if its an html only mail
+ # _html=`mu extract "$_path" | awk '/text\/html/ {print $1; exit}'`
+ # { test "$_html" = "" } || {
+ # mu extract --overwrite --parts="$_html" "$_path"
+ # elinks -dump "$_html".part
+ # rm "$_html".part
+ # return 0 }
+
+ return 0
+}
+
+
+# iterate through the pubdb and publish an rss
+pubdb_render_maildir() {
+ func "publish_render_maildir() $@"
+ md="$1"
+ { test "$md" = "" } && {
+ error "Publish_render_maildir: not found: $md"
+ return 1 }
+ { maildircheck "${md}" } || {
+ error "Publish_render_maildir: not a maildir: $md"
+ return 1 }
+ { test -r "${md}/pub" } || {
+ error "Publish_render_maildir: webnomad not found in ${md}"
+ error "Initialize Webnomad inside the maildir that you want published."
+ return 1 }
+
+ pub="${md}/pub"
+ pubdb="${md}/.pubdb"
+ { test -r "$pubdb" } || {
+ error "Publish_render_maildir: first run update_pubdb for $md"; return 1 }
+
+ ${=mkdir} $TMPDIR/pubdb
+
+ mails=`pubdb_list $md | head -n ${FEED_LIMIT:=30}`
+
+ # source webnomad's configurations
+ { test -r "${md}/config.zsh" } && { source "${md}/config.zsh" }
+
+ cat <<EOF > $pub/atom.xml
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+<title type="text">${TITLE}</title>
+
+<link rel="self" href="${WEB_ROOT}/atom.xml" />
+<link href="${WEB_ROOT}" />
+<id>${WEB_ROOT}/atom.xml</id>
+
+<updated>`date --rfc-3339=seconds`</updated>
+<generator uri="http://www.dyne.org/software/jaro-mail/">JaroMail</generator>
+
+<subtitle type="html">${DESCRIPTION}</subtitle>
+<logo>http://dyne.org/dyne.png</logo>
+
+EOF
+ for m in ${(f)mails}; do
+ _from=`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x From -a`
+ _to=`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x To -a`
+ _fname=`print ${(Q)_from[(ws:,:)2]} | iconv -f utf-8 -t utf-8 -c`
+ _tname=`print ${(Q)_to[(ws:,:)2]} | iconv -f utf-8 -t utf-8 -c`
+ _subject=`hdr "$m" | awk '
+/^Subject:/ { print $0 }
+' |sed -e 's/\&/\&/g ; s/</\>/g ; s/>/\</g'`
+ _date=`hdr "$m" | awk '/^Date:/ { print $0 }'`
+
+ pubdb_getuid "$m"
+
+
+ # if using webnomad write out also the message page
+ { test -d "${md}/webnomad" } && {
+ _upath=`print ${uid} | sed -e 's/\///g'`.html
+ cat <<EOF > "${md}/views/${_upath}"
+<markdown>
+# ${_subject}
+## From: ${_fname}
+### To: ${_tname}
+#### ${_date}
+
+`pubdb_extract_body $m`
+</markdown>
+EOF
+ } # webnomad
+
+ # write out the atom entry
+cat <<EOF >> "$pub"/atom.xml
+
+<entry>
+ <title type="html" xml:lang="en-US">$_subject</title>
+ <link href="${WEB_ROOT}${_upath}" />
+ <id>${WEB_ROOT}${_upath}</id>
+ <updated>`date --rfc-3339=seconds`</updated>
+<content>
+`pubdb_extract_body $m | pubdb_escape_html`
+</content>
+<author>
+ <name>${_fname}</name>
+ <uri>${WEB_ROOT}${_upath}</uri>
+</author>
+<source>
+ <title type="html">${_subject}</title>
+ <subtitle type="html">From: ${_fname}</subtitle>
+ <updated>`date --rfc-3339=seconds`</updated>
+ <link rel="self" href="${WEB_ROOT}${_upath}" />
+ <id>${WEB_ROOT}${_upath}</id>
+</source>
+</entry>
+
+EOF
+ done
+
+ cat <<EOF >> $pub/atom.xml
+</feed>
+EOF
+
+ return 0
+}