Add nix-based build environment

This commit is contained in:
Konstantin Nazarov 2023-09-30 14:41:23 +01:00
parent 3c0f0f157c
commit 2b00252635
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
3 changed files with 501 additions and 422 deletions

26
flake.lock Normal file
View file

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1696078222,
"narHash": "sha256-QBqNSFSPitmvpF2SPqTXbPKo7Wr3kv7cY4zjhCTsHRU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e4f393e78df6fe1986f24b018e60264e89b5c8de",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

53
flake.nix Normal file
View file

@ -0,0 +1,53 @@
{
description = "Nix notes.sh dev environment";
# Flake inputs
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
};
# Flake outputs
outputs = { self, nixpkgs }:
let
# Systems supported
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
"aarch64-linux" # 64-bit ARM Linux
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
# Helper to provide system-specific attributes
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
pkgs = import nixpkgs { inherit system; };
});
make_package = pkgs: pkgs.stdenv.mkDerivation {
name = "notes-sh";
src = self;
nativeBuildInputs = [pkgs.makeWrapper];
installPhase = ''
mkdir -p $out/bin
cp notes.sh $out/bin
'';
postFixup = ''
wrapProgram $out/bin/notes.sh \
--prefix PATH : ${pkgs.lib.makeBinPath (with pkgs; [ gawk findutils gnused coreutils])}
'';
};
in
{
devShells = forAllSystems ({ pkgs }: {
default = pkgs.mkShell {
packages = with pkgs; [
fzf
];
};
});
packages = forAllSystems({ pkgs }: rec {notes-sh = make_package pkgs; default=notes-sh;});
overlays.default = final: prev: {
notes-sh = make_package prev;
};
};
}

844
notes.sh
View file

@ -4,13 +4,13 @@
# #
# Copyright (c) 2021, Konstantin Nazarov <mail@knazarov.com> # Copyright (c) 2021, Konstantin Nazarov <mail@knazarov.com>
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met: # modification, are permitted provided that the following conditions are met:
# #
# 1. Redistributions of source code must retain the above copyright notice, this # 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer. # list of conditions and the following disclaimer.
# #
# 2. Redistributions in binary form must reproduce the above copyright notice, # 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # and/or other materials provided with the distribution.
@ -35,17 +35,17 @@ CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/notes.sh"
EDITOR="${EDITOR:-vim}" EDITOR="${EDITOR:-vim}"
if [ -n "$NOTES_SH_BASEDIR" ]; then if [ -n "$NOTES_SH_BASEDIR" ]; then
BASEDIR="$NOTES_SH_BASEDIR" BASEDIR="$NOTES_SH_BASEDIR"
fi fi
if [ ! -d "$BASEDIR" ]; then if [ ! -d "$BASEDIR" ]; then
mkdir -p "$BASEDIR"/{tmp,new,cur} mkdir -p "$BASEDIR"/{tmp,new,cur}
fi fi
die() { die() {
echo "$@" 1>&2; echo "$@" 1>&2;
exit 1 exit 1
} }
uuid() uuid()
@ -81,528 +81,528 @@ uuid()
} }
utc_timestamp() { utc_timestamp() {
date -u +"%Y-%m-%dT%H:%M:%SZ" date -u +"%Y-%m-%dT%H:%M:%SZ"
} }
gen_boundary() gen_boundary()
{ {
for (( N=0; N < 32; ++N )) for (( N=0; N < 32; ++N ))
do do
B=$(( RANDOM%255 )) B=$(( RANDOM%255 ))
printf '%02x' $B printf '%02x' $B
done done
echo echo
} }
yesno() { yesno() {
PROMPT="$1" PROMPT="$1"
DEFAULT="$2" DEFAULT="$2"
if [[ "$DEFAULT" == "y" ]]; then if [[ "$DEFAULT" == "y" ]]; then
read -p "$PROMPT [Y/n] " -r CHOICE read -p "$PROMPT [Y/n] " -r CHOICE
if [[ "$CHOICE" =~ ^[Nn]$ ]]; then if [[ "$CHOICE" =~ ^[Nn]$ ]]; then
echo "n" echo "n"
else else
echo "y" echo "y"
fi fi
else else
read -p "$PROMPT [y/N] " -r CHOICE read -p "$PROMPT [y/N] " -r CHOICE
if [[ "$CHOICE" =~ ^[Yy]$ ]]; then if [[ "$CHOICE" =~ ^[Yy]$ ]]; then
echo "y" echo "y"
else else
echo "n" echo "n"
fi fi
fi fi
} }
get_headers() { get_headers() {
HEADERS_FILE="$1" HEADERS_FILE="$1"
FILTER="\ FILTER="\
{ \ { \
if (\$0~/^$/) {exit} \ if (\$0~/^$/) {exit} \
if (\$0!~/^[^ ]*: .*$/) {exit} \ if (\$0!~/^[^ ]*: .*$/) {exit} \
print \$0 \ print \$0 \
} \ } \
" "
awk "$FILTER" "$HEADERS_FILE" awk "$FILTER" "$HEADERS_FILE"
} }
get_body() { get_body() {
BODY_FILE="$1" BODY_FILE="$1"
FILTER="\ FILTER="\
{ \ { \
if (body==1) {\ if (body==1) {\
print \$0; print \$0;
}\ }\
if (\$0~/^$/) {body=1} \ if (\$0~/^$/) {body=1} \
if (\$0!~/^[^ ]*: .*$/ && body != 1) {\ if (\$0!~/^[^ ]*: .*$/ && body != 1) {\
body=1; body=1;
print \$0; print \$0;
} \ } \
} \ } \
" "
awk "$FILTER" "$BODY_FILE" awk "$FILTER" "$BODY_FILE"
} }
get_header() { get_header() {
FILE="$1" FILE="$1"
HEADER="$2" HEADER="$2"
grep -m1 "^${HEADER}: " < "$FILE" | sed -n "s/^${HEADER}: \(.*\)$/\1/p" grep -m1 "^${HEADER}: " < "$FILE" | sed -n "s/^${HEADER}: \(.*\)$/\1/p"
} }
find_file_by_id() { find_file_by_id() {
ID="$1" ID="$1"
{ grep -l -r -m1 "^X-Note-Id: $ID$" "$BASEDIR" || true; } | sort| head -1 { grep -l -r -m1 "^X-Note-Id: $ID$" "$BASEDIR" || true; } | sort| head -1
} }
assert_find_file_by_id() { assert_find_file_by_id() {
FILE="$(find_file_by_id "$1")" FILE="$(find_file_by_id "$1")"
if [ ! -f "$FILE" ]; then if [ ! -f "$FILE" ]; then
die "Note with ID <$ID> not found" die "Note with ID <$ID> not found"
fi fi
echo "$FILE" echo "$FILE"
} }
find_files_by_id() { find_files_by_id() {
ID="$1" ID="$1"
grep -l -r -m 1 "^X-Note-Id: $ID$" "$BASEDIR" || true grep -l -r -m 1 "^X-Note-Id: $ID$" "$BASEDIR" || true
} }
get_part() { get_part() {
FILE="$1" FILE="$1"
BOUNDARY="$2" BOUNDARY="$2"
NUM="$3" NUM="$3"
FILTER="\ FILTER="\
BEGIN { \ BEGIN { \
rec=0; \ rec=0; \
body=0; \ body=0; \
} \ } \
{ \ { \
if (\$0==\"--$BOUNDARY\" || \$0==\"--$BOUNDARY--\") { \ if (\$0==\"--$BOUNDARY\" || \$0==\"--$BOUNDARY--\") { \
if (body == 0) { \ if (body == 0) { \
body=1; \ body=1; \
} \ } \
rec=rec+1; \ rec=rec+1; \
} \ } \
else if (body == 1 && rec==$NUM) { \ else if (body == 1 && rec==$NUM) { \
print \$0 \ print \$0 \
} \ } \
}" }"
awk "$FILTER" "$FILE" awk "$FILTER" "$FILE"
} }
unpack_part() { unpack_part() {
FILE="$1" FILE="$1"
DIR="$2" DIR="$2"
MIME_TYPE=$(get_header "$FILE" Content-Type) MIME_TYPE=$(get_header "$FILE" Content-Type)
DISPOSITION=$(get_header "$FILE" Content-Disposition) DISPOSITION=$(get_header "$FILE" Content-Disposition)
if [[ $DISPOSITION == *"attachment"* ]]; then if [[ $DISPOSITION == *"attachment"* ]]; then
ENCODING=$(get_header "$FILE" Content-Transfer-Encoding) ENCODING=$(get_header "$FILE" Content-Transfer-Encoding)
FILENAME=$(echo "$DISPOSITION" | \ FILENAME=$(echo "$DISPOSITION" | \
sed -n 's/^.*filename="\{0,1\}\([^"]*\)"\{0,1\}$/\1/p') sed -n 's/^.*filename="\{0,1\}\([^"]*\)"\{0,1\}$/\1/p')
FILTER="\ FILTER="\
{ \ { \
if (body==1) {print \$0}\ if (body==1) {print \$0}\
if (\$0~/^$/) {body=1} \ if (\$0~/^$/) {body=1} \
} \ } \
" "
if [[ $ENCODING == *"base64"* ]]; then if [[ $ENCODING == *"base64"* ]]; then
awk "$FILTER" "$FILE" | base64 --decode >> "$DIR/$FILENAME" awk "$FILTER" "$FILE" | base64 --decode >> "$DIR/$FILENAME"
else else
awk "$FILTER" "$FILE" >> "$DIR/$FILENAME" awk "$FILTER" "$FILE" >> "$DIR/$FILENAME"
fi fi
elif [[ $MIME_TYPE == *"text/plain"* ]]; then elif [[ $MIME_TYPE == *"text/plain"* ]]; then
FILTER="\ FILTER="\
{ \ { \
if (body==1) {print \$0}\ if (body==1) {print \$0}\
if (\$0~/^$/) {body=1} \ if (\$0~/^$/) {body=1} \
} \ } \
" "
awk "$FILTER" "$FILE" >> "$DIR/note.md" awk "$FILTER" "$FILE" >> "$DIR/note.md"
elif [[ $MIME_TYPE == *"multipart/mixed"* ]]; then elif [[ $MIME_TYPE == *"multipart/mixed"* ]]; then
BOUNDARY=$(echo "$MIME_TYPE" | sed -n 's/^.*boundary="\(.*\)"$/\1/p') BOUNDARY=$(echo "$MIME_TYPE" | sed -n 's/^.*boundary="\(.*\)"$/\1/p')
i=1 i=1
while true; do while true; do
TMP=$(mktemp "$DIR/tmp.XXXXXXXX") TMP=$(mktemp "$DIR/tmp.XXXXXXXX")
get_part "$FILE" "$BOUNDARY" "$i" > "$TMP" get_part "$FILE" "$BOUNDARY" "$i" > "$TMP"
((i++)) ((i++))
if [ ! -s "$TMP" ]; then if [ ! -s "$TMP" ]; then
rm "$TMP" rm "$TMP"
break break
fi fi
#cat "$TMP" #cat "$TMP"
(unpack_part "$TMP" "$DIR") (unpack_part "$TMP" "$DIR")
rm "$TMP" rm "$TMP"
done done
elif [[ $MIME_TYPE == *"multipart/related"* ]]; then elif [[ $MIME_TYPE == *"multipart/related"* ]]; then
echo "multipart/related not yet supported" echo "multipart/related not yet supported"
exit 1 exit 1
fi fi
} }
unpack_mime() { unpack_mime() {
FILE="$1" FILE="$1"
DIR="$2" DIR="$2"
get_headers "$FILE" | grep -v "^Content-Type\|^Content-Disposition\|^Date\|^MIME-Version" >> "$DIR/note.md" get_headers "$FILE" | grep -v "^Content-Type\|^Content-Disposition\|^Date\|^MIME-Version" >> "$DIR/note.md"
echo "" >> "$DIR/note.md" echo "" >> "$DIR/note.md"
TMP=$(mktemp "$DIR/tmp.XXXXXXXX") TMP=$(mktemp "$DIR/tmp.XXXXXXXX")
cat "$FILE" > "$TMP" cat "$FILE" > "$TMP"
(unpack_part "$TMP" "$DIR") (unpack_part "$TMP" "$DIR")
rm "$TMP" rm "$TMP"
} }
pack_part() { pack_part() {
PART_FILE="$1" PART_FILE="$1"
CONTENT_TYPE="$(file -b --mime-type "$PART_FILE")" CONTENT_TYPE="$(file -b --mime-type "$PART_FILE")"
echo "Content-Disposition: attachment; filename=\"$(basename "$PART_FILE")\"" echo "Content-Disposition: attachment; filename=\"$(basename "$PART_FILE")\""
if [[ "$CONTENT_TYPE" =~ "text/" ]]; then if [[ "$CONTENT_TYPE" =~ "text/" ]]; then
echo "Content-Type: text/plain" echo "Content-Type: text/plain"
echo echo
cat "$PART_FILE" cat "$PART_FILE"
else else
echo "Content-Type: $CONTENT_TYPE" echo "Content-Type: $CONTENT_TYPE"
echo "Content-Transfer-Encoding: base64" echo "Content-Transfer-Encoding: base64"
echo echo
base64 | fold -w 76 < "$PART_FILE" base64 | fold -w 76 < "$PART_FILE"
fi fi
} }
pack_mime() { pack_mime() {
DIR="$1" DIR="$1"
FILE="$2" FILE="$2"
FILE_COUNT="$(find "$DIR/" -type f | wc -l | tr -d " ")" FILE_COUNT="$(find "$DIR/" -type f | wc -l | tr -d " ")"
MIME_TIMESTAMP=$(LC_ALL="en_US.UTF-8" date "+$DATE_FORMAT") MIME_TIMESTAMP=$(LC_ALL="en_US.UTF-8" date "+$DATE_FORMAT")
if [[ "$FILE_COUNT" == "1" ]]; then if [[ "$FILE_COUNT" == "1" ]]; then
{ {
echo "MIME-Version: 1.0" echo "MIME-Version: 1.0"
echo "Date: $MIME_TIMESTAMP" echo "Date: $MIME_TIMESTAMP"
echo "Content-Type: text/plain; charset=utf-8" echo "Content-Type: text/plain; charset=utf-8"
echo "Content-Disposition: inline" echo "Content-Disposition: inline"
cat "$DIR/note.md" cat "$DIR/note.md"
} >> "$FILE" } >> "$FILE"
return return
fi fi
BOUNDARY="$(gen_boundary)" BOUNDARY="$(gen_boundary)"
{ {
echo "MIME-Version: 1.0" echo "MIME-Version: 1.0"
echo "Date: $MIME_TIMESTAMP" echo "Date: $MIME_TIMESTAMP"
echo "Content-Type: multipart/mixed; boundary=\"$BOUNDARY\"" echo "Content-Type: multipart/mixed; boundary=\"$BOUNDARY\""
get_headers "$DIR/note.md" get_headers "$DIR/note.md"
echo echo
echo "--$BOUNDARY" echo "--$BOUNDARY"
echo "Content-Type: text/plain; charset=utf-8" echo "Content-Type: text/plain; charset=utf-8"
echo "Content-Disposition: inline" echo "Content-Disposition: inline"
echo echo
get_body "$DIR/note.md" get_body "$DIR/note.md"
} >> "$FILE" } >> "$FILE"
find "$DIR/" -type f ! -name 'note.md' | while read -r FN find "$DIR/" -type f ! -name 'note.md' | while read -r FN
do do
{ {
echo "--$BOUNDARY" echo "--$BOUNDARY"
pack_part "$FN" pack_part "$FN"
} >> "$FILE" } >> "$FILE"
done done
echo "--$BOUNDARY--" >> "$FILE" echo "--$BOUNDARY--" >> "$FILE"
} }
input_note() { input_note() {
INP="$1" INP="$1"
OUTP="$2" OUTP="$2"
DIR="$(mktemp -d)" DIR="$(mktemp -d)"
ENTRY_FILE="$DIR/note.md" ENTRY_FILE="$DIR/note.md"
MIME_TIMESTAMP=$(LC_ALL="en_US.UTF-8" date "+$DATE_FORMAT") MIME_TIMESTAMP=$(LC_ALL="en_US.UTF-8" date "+$DATE_FORMAT")
UTC_TIMESTAMP=$(utc_timestamp) UTC_TIMESTAMP=$(utc_timestamp)
if [ -n "$INP" ] && [ ! -f "$INP" ] && [ ! -d "$INP" ]; then if [ -n "$INP" ] && [ ! -f "$INP" ] && [ ! -d "$INP" ]; then
die "File or directory doesn't exist: $INP" die "File or directory doesn't exist: $INP"
fi fi
if [ -f "$INP" ]; then if [ -f "$INP" ]; then
{ {
get_headers "$INP" get_headers "$INP"
echo "" echo ""
get_body "$INP" get_body "$INP"
} >> "$DIR/note.md" } >> "$DIR/note.md"
elif [ -d "$INP" ]; then elif [ -d "$INP" ]; then
if [ ! -f "$INP/note.md" ]; then if [ ! -f "$INP/note.md" ]; then
die "File doesn't exist: $INP/note.md" die "File doesn't exist: $INP/note.md"
fi fi
cp -n "$INP"/* "$DIR/" || true cp -n "$INP"/* "$DIR/" || true
elif [ -t 0 ]; then elif [ -t 0 ]; then
cat > "$ENTRY_FILE" <<- EOF cat > "$ENTRY_FILE" <<- EOF
X-Date: $UTC_TIMESTAMP X-Date: $UTC_TIMESTAMP
X-Note-Id: $(uuid) X-Note-Id: $(uuid)
Subject: Subject:
EOF EOF
OLD_DIR="$(pwd)" OLD_DIR="$(pwd)"
cd "$DIR" cd "$DIR"
"$EDITOR" "$ENTRY_FILE" $EDITOR "$ENTRY_FILE"
cd "$OLD_DIR" cd "$OLD_DIR"
else else
while read -r line ; do while read -r line ; do
echo "$line" >> "$ENTRY_FILE" echo "$line" >> "$ENTRY_FILE"
done done
fi fi
MERGED_ENTRY_FILE="$(mktemp)" MERGED_ENTRY_FILE="$(mktemp)"
HEADERS=$(get_headers "$ENTRY_FILE") HEADERS=$(get_headers "$ENTRY_FILE")
{ {
echo "$HEADERS" | grep -q "^X-Date:" || echo "X-Date: $UTC_TIMESTAMP" echo "$HEADERS" | grep -q "^X-Date:" || echo "X-Date: $UTC_TIMESTAMP"
echo "$HEADERS" | grep -q "^X-Note-Id:" || echo "X-Note-Id: $(uuid)" echo "$HEADERS" | grep -q "^X-Note-Id:" || echo "X-Note-Id: $(uuid)"
echo "$HEADERS" echo "$HEADERS"
echo "$HEADERS" | grep -q "^Subject:" || echo "Subject: " echo "$HEADERS" | grep -q "^Subject:" || echo "Subject: "
echo "" echo ""
get_body "$ENTRY_FILE" get_body "$ENTRY_FILE"
} > "$MERGED_ENTRY_FILE" } > "$MERGED_ENTRY_FILE"
mv "$MERGED_ENTRY_FILE" "$ENTRY_FILE" mv "$MERGED_ENTRY_FILE" "$ENTRY_FILE"
pack_mime "$DIR" "$OUTP" pack_mime "$DIR" "$OUTP"
rm -rf "$DIR" rm -rf "$DIR"
} }
remove_notes_by_id() { remove_notes_by_id() {
ID="$1" ID="$1"
find_files_by_id "$ID" | while read -r FN find_files_by_id "$ID" | while read -r FN
do do
rm "$FN" rm "$FN"
done done
} }
notes_equal() { notes_equal() {
NOTE1="$1" NOTE1="$1"
NOTE2="$2" NOTE2="$2"
MIME_TYPE1=$(get_header "$NOTE1" Content-Type) MIME_TYPE1=$(get_header "$NOTE1" Content-Type)
MIME_TYPE2=$(get_header "$NOTE2" Content-Type) MIME_TYPE2=$(get_header "$NOTE2" Content-Type)
BOUNDARY1=$(echo "$MIME_TYPE1" | sed -n 's/^.*boundary="\(.*\)"$/\1/p') BOUNDARY1=$(echo "$MIME_TYPE1" | sed -n 's/^.*boundary="\(.*\)"$/\1/p')
BOUNDARY2=$(echo "$MIME_TYPE2" | sed -n 's/^.*boundary="\(.*\)"$/\1/p') BOUNDARY2=$(echo "$MIME_TYPE2" | sed -n 's/^.*boundary="\(.*\)"$/\1/p')
FILTER1="^Date:" FILTER1="^Date:"
FILTER2="^Date:" FILTER2="^Date:"
if [ -n "$BOUNDARY1" ]; then if [ -n "$BOUNDARY1" ]; then
FILTER1="^Date:\|$BOUNDARY1" FILTER1="^Date:\|$BOUNDARY1"
fi fi
if [ -n "$BOUNDARY2" ]; then if [ -n "$BOUNDARY2" ]; then
FILTER2="^Date:\|$BOUNDARY2" FILTER2="^Date:\|$BOUNDARY2"
fi fi
NOTE1_S="$(mktemp)" NOTE1_S="$(mktemp)"
grep -v "$FILTER1" "$NOTE1" > "$NOTE1_S" grep -v "$FILTER1" "$NOTE1" > "$NOTE1_S"
if grep -v "$FILTER2" "$NOTE2" | cmp -s "$NOTE1_S"; then if grep -v "$FILTER2" "$NOTE2" | cmp -s "$NOTE1_S"; then
rm "$NOTE1_S" rm "$NOTE1_S"
return 0 return 0
else else
rm "$NOTE1_S" rm "$NOTE1_S"
return 1 return 1
fi fi
} }
new_entry() { new_entry() {
OUTP="$(mktemp)" OUTP="$(mktemp)"
input_note "$1" "$OUTP" input_note "$1" "$OUTP"
if [ ! -s "$OUTP" ]; then if [ ! -s "$OUTP" ]; then
rm "$OUTP" rm "$OUTP"
return return
fi fi
NOTE_ID="$(get_header "$OUTP" X-Note-Id)" NOTE_ID="$(get_header "$OUTP" X-Note-Id)"
OLD_NOTE="$(find_file_by_id "$NOTE_ID")" OLD_NOTE="$(find_file_by_id "$NOTE_ID")"
if [ -n "$OLD_NOTE" ] && notes_equal "$OLD_NOTE" "$OUTP"; then if [ -n "$OLD_NOTE" ] && notes_equal "$OLD_NOTE" "$OUTP"; then
return return
fi fi
remove_notes_by_id "$NOTE_ID" remove_notes_by_id "$NOTE_ID"
UNIX_TIMESTAMP=$(date "+%s") UNIX_TIMESTAMP=$(date "+%s")
HOSTNAME=$(hostname -s) HOSTNAME=$(hostname -s)
FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S" FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S"
mv "$OUTP" "$BASEDIR/cur/$FILENAME" mv "$OUTP" "$BASEDIR/cur/$FILENAME"
} }
edit_entry() { edit_entry() {
ID="$1" ID="$1"
FILENAME="$(assert_find_file_by_id "$ID")" FILENAME="$(assert_find_file_by_id "$ID")"
DIR="$CACHE_DIR/$ID" DIR="$CACHE_DIR/$ID"
if [ -d "$DIR" ] && [ -f "$DIR/note.md" ]; then if [ -d "$DIR" ] && [ -f "$DIR/note.md" ]; then
RESUME_EDITING=$(yesno "Unsaved changes found for this note. Resume editing?" y) RESUME_EDITING=$(yesno "Unsaved changes found for this note. Resume editing?" y)
fi fi
if [ "$RESUME_EDITING" != "y" ]; then if [ "$RESUME_EDITING" != "y" ]; then
rm -rf "$DIR" rm -rf "$DIR"
mkdir -p "$DIR" mkdir -p "$DIR"
unpack_mime "$FILENAME" "$DIR" unpack_mime "$FILENAME" "$DIR"
fi fi
OLD_DIR="$(pwd)" OLD_DIR="$(pwd)"
cd "$DIR" cd "$DIR"
if ! "$EDITOR" "$DIR/note.md"; then if ! $EDITOR "$DIR/note.md"; then
die "Editor returned non-zero exit code. Leaving the note untouched." die "Editor returned non-zero exit code. Leaving the note untouched."
fi fi
cd "$OLD_DIR" cd "$OLD_DIR"
UNIX_TIMESTAMP=$(date "+%s") UNIX_TIMESTAMP=$(date "+%s")
HOSTNAME=$(hostname -s) HOSTNAME=$(hostname -s)
RESULT=$(mktemp) RESULT=$(mktemp)
pack_mime "$DIR" "$RESULT" pack_mime "$DIR" "$RESULT"
if ! notes_equal "$RESULT" "$FILENAME"; then if ! notes_equal "$RESULT" "$FILENAME"; then
DEST_FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S" DEST_FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S"
mv "$RESULT" "$BASEDIR/cur/$DEST_FILENAME" mv "$RESULT" "$BASEDIR/cur/$DEST_FILENAME"
rm "$FILENAME" rm "$FILENAME"
fi fi
rm -rf "$DIR" rm -rf "$DIR"
} }
list_entries() { list_entries() {
FILTER="\ FILTER="\
BEGIN { \ BEGIN { \
message_id=0; \ message_id=0; \
subject=0; \ subject=0; \
date=0; \ date=0; \
} \ } \
match(\$0, /^X-Note-Id: .*$/) { \ match(\$0, /^X-Note-Id: .*$/) { \
if (message_id != 0) { \ if (message_id != 0) { \
if (subject !=0 && date != 0)\ if (subject !=0 && date != 0)\
print date, message_id, subject; \ print date, message_id, subject; \
subject = 0; \ subject = 0; \
date = 0; \ date = 0; \
};\ };\
message_id = substr(\$0, 12, RLENGTH-11); \ message_id = substr(\$0, 12, RLENGTH-11); \
} \ } \
match(\$0, /^Subject: .*$/) { \ match(\$0, /^Subject: .*$/) { \
if (subject != 0) { \ if (subject != 0) { \
if (message_id != 0 && date != 0)\ if (message_id != 0 && date != 0)\
print date, message_id, subject; \ print date, message_id, subject; \
message_id = 0; \ message_id = 0; \
date = 0; \ date = 0; \
}; \ }; \
subject = substr(\$0, 10, RLENGTH-9); \ subject = substr(\$0, 10, RLENGTH-9); \
} \ } \
match(\$0, /^X-Date: .*$/) { \ match(\$0, /^X-Date: .*$/) { \
if (date != 0) { \ if (date != 0) { \
if (message_id != 0 && subject != 0)\ if (message_id != 0 && subject != 0)\
print date, message_id, subject; \ print date, message_id, subject; \
subject = 0; \ subject = 0; \
message_id = 0; \ message_id = 0; \
}; \ }; \
date = substr(\$0, 9, RLENGTH-8); \ date = substr(\$0, 9, RLENGTH-8); \
} \ } \
END { \ END { \
if (message_id != 0 && subject != 0 && date != 0)\ if (message_id != 0 && subject != 0 && date != 0)\
print date, message_id, subject;\ print date, message_id, subject;\
}\ }\
" "
grep -r -h "^Subject:\|^X-Note-Id:\|^X-Date:" "$BASEDIR" | awk "$FILTER" | sort | cut -d " " -f "2-" grep -r -h "^Subject:\|^X-Note-Id:\|^X-Date:" "$BASEDIR" | awk "$FILTER" | sort | cut -d " " -f "2-"
} }
export_note() { export_note() {
ID="$1" ID="$1"
FILENAME="$(assert_find_file_by_id "$ID")" FILENAME="$(assert_find_file_by_id "$ID")"
DIR="$2" DIR="$2"
if [ -z "$DIR" ]; then if [ -z "$DIR" ]; then
DIR="$(mktemp -d)" DIR="$(mktemp -d)"
unpack_mime "$FILENAME" "$DIR" unpack_mime "$FILENAME" "$DIR"
cat "$DIR/note.md" cat "$DIR/note.md"
rm -rf "$DIR" rm -rf "$DIR"
else else
unpack_mime "$FILENAME" "$DIR" unpack_mime "$FILENAME" "$DIR"
fi fi
} }
get_raw_graph() { get_raw_graph() {
UUID_RE="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" UUID_RE="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
FILTER="\ FILTER="\
BEGIN { \ BEGIN { \
message_id=0; \ message_id=0; \
subject=0; \ subject=0; \
} \ } \
match(\$0, /^X-Note-Id: .*$/) { \ match(\$0, /^X-Note-Id: .*$/) { \
if (message_id != 0) { \ if (message_id != 0) { \
if (subject !=0)\ if (subject !=0)\
print \"node\", message_id, subject; \ print \"node\", message_id, subject; \
subject = 0 \ subject = 0 \
};\ };\
message_id = substr(\$0, 12, RLENGTH-11) \ message_id = substr(\$0, 12, RLENGTH-11) \
} \ } \
match(\$0, /^Subject: .*$/) { \ match(\$0, /^Subject: .*$/) { \
if (subject != 0) { \ if (subject != 0) { \
if (message_id != 0)\ if (message_id != 0)\
print \"node\", message_id, subject; \ print \"node\", message_id, subject; \
message_id = 0 \ message_id = 0 \
}; \ }; \
subject = substr(\$0, 10, RLENGTH-9) \ subject = substr(\$0, 10, RLENGTH-9) \
} \ } \
match(\$0, /^note:\/\/.*$/) { \ match(\$0, /^note:\/\/.*$/) { \
link_to = substr(\$0, 8, RLENGTH-7); \ link_to = substr(\$0, 8, RLENGTH-7); \
if (subject != 0 && message_id != 0) { \ if (subject != 0 && message_id != 0) { \
print \"link\", message_id, link_to; \ print \"link\", message_id, link_to; \
}; \ }; \
} \ } \
END { \ END { \
if (message_id != 0 && subject != 0)\ if (message_id != 0 && subject != 0)\
print \"node\", message_id, subject;\ print \"node\", message_id, subject;\
}\ }\
" "
grep -E -i -o -r -h "^X-Note-Id: $UUID_RE|^Subject.*$|note://$UUID_RE" \ grep -E -i -o -r -h "^X-Note-Id: $UUID_RE|^Subject.*$|note://$UUID_RE" \
"$BASEDIR"/cur | awk "$FILTER" "$BASEDIR"/cur | awk "$FILTER"
} }
get_graph() { get_graph() {
UUIDLEN=36 UUIDLEN=36
FILTER="\ FILTER="\
BEGIN { \ BEGIN { \
print \"graph notes {\" \ print \"graph notes {\" \
} \ } \
{\ {\
if (\$1 == \"node\") {\ if (\$1 == \"node\") {\
printf \" \\\"%s\\\" \", \$2;\ printf \" \\\"%s\\\" \", \$2;\
printf \"[label=\\\"%s\\\"]\", substr(\$0, $UUIDLEN + 7, length(\$0) - $UUIDLEN - 5);\ printf \"[label=\\\"%s\\\"]\", substr(\$0, $UUIDLEN + 7, length(\$0) - $UUIDLEN - 5);\
printf \";\\n\";\ printf \";\\n\";\
}\ }\
if (\$1 == \"link\") {\ if (\$1 == \"link\") {\
printf \" \\\"%s\\\" -- \\\"%s\\\";\\n\", \$2, \$3;\ printf \" \\\"%s\\\" -- \\\"%s\\\";\\n\", \$2, \$3;\
}\ }\
}\ }\
END { \ END { \
print \"}\" \ print \"}\" \
} \ } \
" "
get_raw_graph | sort -r | sed 's/"/\\"/g' | awk "$FILTER" get_raw_graph | sort -r | sed 's/"/\\"/g' | awk "$FILTER"
} }
usage() { usage() {
@ -627,18 +627,18 @@ while (( "$#" )); do
edit_entry "$2" edit_entry "$2"
exit 0 exit 0
;; ;;
-E|--export) -E|--export)
if [ -z "$2" ]; then if [ -z "$2" ]; then
echo "Misssing arguments for $1" echo "Misssing arguments for $1"
exit 1 exit 1
fi fi
export_note "$2" "$3" export_note "$2" "$3"
exit 0 exit 0
;; ;;
-g|--graph) -g|--graph)
get_graph get_graph
exit 0 exit 0
;; ;;
*) *)
usage usage
exit 1 exit 1