Initial verion of notes.sh
This commit is contained in:
commit
ec46a11cc6
2 changed files with 234 additions and 0 deletions
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# notes.sh: plain-text notes with IMAP synchronization
|
||||||
|
|
||||||
|
This project allows you to keep plaintext notes, and attach files to them (like images, PDFs, etc). The plaintext stays plaintext, easily greppable, and with every note in its own file.
|
||||||
|
|
||||||
|
What makes it special is that it uses a Maildir format to store notes. This allows you to easily sync your notes with any mail server, and have your notes accessible on the go through your email client.
|
||||||
|
|
||||||
|
NB: This is alpha software, use with caution.
|
227
notes.sh
Executable file
227
notes.sh
Executable file
|
@ -0,0 +1,227 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DATE_FORMAT="%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
BASEDIR=~/Maildir/personal/Notes
|
||||||
|
PID=$$
|
||||||
|
|
||||||
|
|
||||||
|
if [ ! -d "$BASEDIR" ]; then
|
||||||
|
mkdir -p "$BASEDIR"/{tmp,new,cur}
|
||||||
|
fi
|
||||||
|
|
||||||
|
uuid()
|
||||||
|
{
|
||||||
|
local N B T
|
||||||
|
|
||||||
|
for (( N=0; N < 16; ++N ))
|
||||||
|
do
|
||||||
|
B=$(( $RANDOM%255 ))
|
||||||
|
|
||||||
|
if (( N == 6 ))
|
||||||
|
then
|
||||||
|
printf '4%x' $(( B%15 ))
|
||||||
|
elif (( N == 8 ))
|
||||||
|
then
|
||||||
|
local C='89ab'
|
||||||
|
printf '%c%x' ${C:$(( $RANDOM%${#C} )):1} $(( B%15 ))
|
||||||
|
else
|
||||||
|
printf '%02x' $B
|
||||||
|
fi
|
||||||
|
|
||||||
|
for T in 3 5 7 9
|
||||||
|
do
|
||||||
|
if (( T == N ))
|
||||||
|
then
|
||||||
|
printf '-'
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
get_header() {
|
||||||
|
FILE="$1"
|
||||||
|
HEADER="$2"
|
||||||
|
echo $(grep -m1 "^${HEADER}: " < "$FILE" | sed -n "s/^${HEADER}: \(.*\)$/\1/p")
|
||||||
|
}
|
||||||
|
|
||||||
|
get_part() {
|
||||||
|
FILE="$1"
|
||||||
|
BOUNDARY="$2"
|
||||||
|
NUM="$3"
|
||||||
|
FILTER="\
|
||||||
|
BEGIN { \
|
||||||
|
rec=0; \
|
||||||
|
body=0; \
|
||||||
|
} \
|
||||||
|
{ \
|
||||||
|
if (\$0==\"--$BOUNDARY\" || \$0==\"--$BOUNDARY--\") { \
|
||||||
|
if (body == 0) { \
|
||||||
|
body=1; \
|
||||||
|
} \
|
||||||
|
rec=rec+1; \
|
||||||
|
} \
|
||||||
|
else if (body == 1 && rec==$NUM) { \
|
||||||
|
print \$0 \
|
||||||
|
} \
|
||||||
|
}"
|
||||||
|
|
||||||
|
awk "$FILTER" "$FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
unpack_part() {
|
||||||
|
FILE="$1"
|
||||||
|
DIR="$2"
|
||||||
|
MIME_TYPE=$(get_header "$FILE" Content-Type)
|
||||||
|
DISPOSITION=$(get_header "$FILE" Content-Disposition)
|
||||||
|
|
||||||
|
if [[ $DISPOSITION == *"attachment"* ]]; then
|
||||||
|
ENCODING=$(get_header "$FILE" Content-Transfer-Encoding)
|
||||||
|
FILENAME=$(echo $DISPOSITION | \
|
||||||
|
sed -n 's/^.*filename="\{0,1\}\(.*\)"\{0,1\}$/\1/p')
|
||||||
|
FILTER="\
|
||||||
|
{ \
|
||||||
|
if (body==1) {print \$0}\
|
||||||
|
if (\$0~/^$/) {body=1} \
|
||||||
|
} \
|
||||||
|
"
|
||||||
|
|
||||||
|
if [[ $ENCODING == *"base64"* ]]; then
|
||||||
|
awk "$FILTER" "$FILE" | base64 --decode >> "$DIR/$FILENAME"
|
||||||
|
else
|
||||||
|
awk "$FILTER" "$FILE" >> "$DIR/$FILENAME"
|
||||||
|
fi
|
||||||
|
elif [[ $MIME_TYPE == *"text/plain"* ]]; then
|
||||||
|
FILTER="\
|
||||||
|
{ \
|
||||||
|
if (body==1) {print \$0}\
|
||||||
|
if (\$0~/^$/) {body=1} \
|
||||||
|
} \
|
||||||
|
"
|
||||||
|
awk "$FILTER" "$FILE" >> "$DIR/note.md"
|
||||||
|
elif [[ $MIME_TYPE == *"multipart/mixed"* ]]; then
|
||||||
|
BOUNDARY=$(echo $MIME_TYPE | sed -n 's/^.*boundary="\(.*\)"$/\1/p')
|
||||||
|
i=1
|
||||||
|
while true; do
|
||||||
|
TMP=$(mktemp --tmpdir="$DIR")
|
||||||
|
get_part "$FILE" "$BOUNDARY" "$i" > "$TMP"
|
||||||
|
((i++))
|
||||||
|
if [ ! -s "$TMP" ]; then
|
||||||
|
rm "$TMP"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
#cat "$TMP"
|
||||||
|
(unpack_part "$TMP" "$DIR")
|
||||||
|
rm "$TMP"
|
||||||
|
done
|
||||||
|
elif [[ $MIME_TYPE == *"multipart/related"* ]]; then
|
||||||
|
echo "multipart/related not yet supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
unpack_mime() {
|
||||||
|
FILE="$1"
|
||||||
|
DIR="$2"
|
||||||
|
|
||||||
|
DATE=$(get_header "$FILE" Date)
|
||||||
|
SUBJECT=$(get_header "$FILE" Subject)
|
||||||
|
MIME_TYPE=$(get_header "$FILE" Content-Type)
|
||||||
|
MESSAGE_ID=$(get_header "$FILE" Message-Id)
|
||||||
|
|
||||||
|
echo "Date: $DATE" > "$DIR/note.md"
|
||||||
|
if [ ! -z "$MESSAGE_ID" ]; then
|
||||||
|
echo "Message-Id: $MESSAGE_ID" >> "$DIR/note.md"
|
||||||
|
fi
|
||||||
|
echo "Subject: $SUBJECT" >> "$DIR/note.md"
|
||||||
|
echo "" >> "$DIR/note.md"
|
||||||
|
|
||||||
|
TMP=$(mktemp --tmpdir="$DIR")
|
||||||
|
cat "$FILE" > "$TMP"
|
||||||
|
|
||||||
|
(unpack_part "$TMP" "$DIR")
|
||||||
|
rm "$TMP"
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entry() {
|
||||||
|
ENTRY_FILE=$(mktemp)
|
||||||
|
|
||||||
|
vim -c ":set syntax=markdown" \
|
||||||
|
-c "1 s/^/# /" \
|
||||||
|
-c "norm $" \
|
||||||
|
"$ENTRY_FILE"
|
||||||
|
|
||||||
|
if [ -s "$ENTRY_FILE" ]; then
|
||||||
|
MIME_TIMESTAMP=$(LC_ALL="en_US.UTF-8" date "+$DATE_FORMAT")
|
||||||
|
UNIX_TIMESTAMP=$(date "+%s")
|
||||||
|
HOSTNAME=$(hostname -s)
|
||||||
|
SUBJECT=$(head -1 "$ENTRY_FILE")
|
||||||
|
SUBJECT=${SUBJECT#"# "}
|
||||||
|
|
||||||
|
RESULT=$(mktemp)
|
||||||
|
echo "Date: $MIME_TIMESTAMP" >> "$RESULT"
|
||||||
|
echo "MIME-Version: 1.0" >> "$RESULT"
|
||||||
|
echo "Content-Type: text/plain; charset=utf-8" >> "$RESULT"
|
||||||
|
echo "Content-Disposition: inline" >> "$RESULT"
|
||||||
|
echo "Message-Id: <$(uuid)@notes.sh>" >> "$RESULT"
|
||||||
|
echo "Subject: $SUBJECT" >> "$RESULT"
|
||||||
|
echo "" >> "$RESULT"
|
||||||
|
sed "1d" < "$ENTRY_FILE" >> "$RESULT"
|
||||||
|
|
||||||
|
#cat "$RESULT"
|
||||||
|
|
||||||
|
FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S"
|
||||||
|
mv "$RESULT" "$BASEDIR/cur/$FILENAME"
|
||||||
|
|
||||||
|
echo "done"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
edit_entry() {
|
||||||
|
unpack_mime "$1" ~/scratch/notes
|
||||||
|
}
|
||||||
|
|
||||||
|
list_entries() {
|
||||||
|
grep -m1 -r "^Subject:" "$BASEDIR" | sed -n 's/^\(.*\):Subject: \(.*\)$/\1:\2/p'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "$0 {--new,--list,--edit,--help}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (( "$#" )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--new)
|
||||||
|
new_entry
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-l|--list)
|
||||||
|
list_entries
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-e|--edit)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Misssing argument for $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
edit_entry "$2"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
usage
|
||||||
|
exit 1
|
Loading…
Reference in a new issue