improvement: externalized files
This commit is contained in:
parent
8970e89cc0
commit
5a3668d6a9
@ -5,7 +5,15 @@ GREEN="\033[0;32m"
|
|||||||
OFF="\033[m"
|
OFF="\033[m"
|
||||||
NAME="fzf-vcal"
|
NAME="fzf-vcal"
|
||||||
SRC="./src/main.sh"
|
SRC="./src/main.sh"
|
||||||
echo "🐔 ${GREEN}Building${OFF} ${BOLD}$NAME${OFF}"
|
|
||||||
sed -E 's|@@include (.+)$|cat \1|e' "$SRC" >"$NAME"
|
tmpdir=$(mktemp -d)
|
||||||
|
echo "🐔 ${GREEN}Internalize sourced files${OFF}"
|
||||||
|
sed -E 's|\. "([^$].+)"$|cat src/\1|e' "$SRC" >"$tmpdir/1.sh"
|
||||||
|
echo "🥚 ${GREEN}Internalize awk scripts${OFF}"
|
||||||
|
sed -E 's|@@include (.+)$|cat src/\1|e' "$tmpdir/1.sh" >"$tmpdir/2.sh"
|
||||||
|
echo "🐔 ${GREEN}Internalize awk libraries${OFF}"
|
||||||
|
sed -E 's|@include "(.+)"$|cat src/\1|e' "$tmpdir/2.sh" >"$NAME"
|
||||||
|
echo "🥚 ${GREEN}Make executable and cleanup${OFF}"
|
||||||
chmod +x "$NAME"
|
chmod +x "$NAME"
|
||||||
echo "🥚 ${GREEN}Done${OFF}"
|
rm -rf "$tmpdir"
|
||||||
|
echo "🍳 ${GREEN}Done:${OFF} Sucessfully built ${BOLD}${GREEN}$NAME${OFF}"
|
||||||
|
@ -3,80 +3,10 @@
|
|||||||
##
|
##
|
||||||
## @assign collection_labels: See configuration of the current program.
|
## @assign collection_labels: See configuration of the current program.
|
||||||
|
|
||||||
|
@include "lib/awk/icalendar.awk"
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
|
|
||||||
# Unescape string
|
|
||||||
#
|
|
||||||
# @local variables: i, c, c2, res
|
|
||||||
# @input str: String
|
|
||||||
# @return: Unescaped string
|
|
||||||
function unescape(str, i, c, c2, res) {
|
|
||||||
for(i=1; i<=length(str);i++) {
|
|
||||||
c = substr(str, i, 1)
|
|
||||||
if (c != "\\") {
|
|
||||||
res = res c
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
c2 = substr(str, i, 1)
|
|
||||||
if (c2 == "n" || c2 == "N") {
|
|
||||||
res = res "\n"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
# Alternatively, c2 is "\\" or "," or ";". In each case, append res with
|
|
||||||
# c2. If the strings has been escaped correctly, then the character c2
|
|
||||||
# cannot be anything else. To be fail-safe, simply append res with c2.
|
|
||||||
res = res c2
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
# Isolate content part of an iCalendar line, and unescape.
|
|
||||||
#
|
|
||||||
# @input str: String
|
|
||||||
# @return: Unescaped content part
|
|
||||||
function getcontent(str) {
|
|
||||||
return unescape(substr(str, index(str, ":") + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
# Time-zone aware parsing of the date/date-time entry at the current record.
|
|
||||||
#
|
|
||||||
# @local variables: dt
|
|
||||||
# @return: date or date-time string that can be used in date (1)
|
|
||||||
function parse( dt) {
|
|
||||||
# Get timezone information
|
|
||||||
for (i=2; i<NF-1; i+=2) {
|
|
||||||
if ($i == "TZID") {
|
|
||||||
dt = "TZ=\"" $(i+1) "\" "
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Get date/date-time
|
|
||||||
return length($NF) == 8 ?
|
|
||||||
dt $NF :
|
|
||||||
dt gensub(/^([0-9]{8})T([0-9]{2})([0-9]{2})([0-9]{2})(Z)?$/, "\\1 \\2:\\3:\\4\\5", "g", $NF)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Map iCalendar duration specification into the format to be used in date (1).
|
|
||||||
#
|
|
||||||
# @local variables: dt, dta, i, n, a, seps
|
|
||||||
# @input duration: iCalendar duration string
|
|
||||||
# @return: relative-date/date-time specification to be used in date (1)
|
|
||||||
function parse_duration(duration, dt, dta, i, n, a, seps) {
|
|
||||||
n = split(duration, a, /[PTWHMSD]/, seps)
|
|
||||||
for (i=2; i<=n; i++) {
|
|
||||||
if(seps[i] == "W") dta["weeks"] = a[i]
|
|
||||||
if(seps[i] == "H") dta["hours"] = a[i]
|
|
||||||
if(seps[i] == "M") dta["minutes"] = a[i]
|
|
||||||
if(seps[i] == "S") dta["seconds"] = a[i]
|
|
||||||
if(seps[i] == "D") dta["days"] = a[i]
|
|
||||||
}
|
|
||||||
dt = a[1] ? a[1] : "+"
|
|
||||||
for (i in dta)
|
|
||||||
dt = dt " " dta[i] " " i
|
|
||||||
return dt
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get relative file path.
|
# Get relative file path.
|
||||||
#
|
#
|
||||||
# @local variables: n, a
|
# @local variables: n, a
|
||||||
|
@ -3,41 +3,8 @@
|
|||||||
##
|
##
|
||||||
## @assign field: Field name
|
## @assign field: Field name
|
||||||
|
|
||||||
# Unescape string
|
@include "lib/awk/icalendar.awk"
|
||||||
#
|
|
||||||
# @local variables: i, c, c2, res
|
|
||||||
# @input str: String
|
|
||||||
# @return: Unescaped string
|
|
||||||
function unescape(str, i, c, c2, res) {
|
|
||||||
for(i=1; i<=length(str);i++) {
|
|
||||||
c = substr(str, i, 1)
|
|
||||||
if (c != "\\") {
|
|
||||||
res = res c
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
c2 = substr(str, i, 1)
|
|
||||||
if (c2 == "n" || c2 == "N") {
|
|
||||||
res = res "\n"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
# Alternatively, c2 is "\\" or "," or ";". In each case, append res with
|
|
||||||
# c2. If the strings has been escaped correctly, then the character c2
|
|
||||||
# cannot be anything else. To be fail-safe, simply append res with c2.
|
|
||||||
res = res c2
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
# Isolate content part of an iCalendar line, and unescape.
|
|
||||||
#
|
|
||||||
# @input str: String
|
|
||||||
# @return: Unescaped content part
|
|
||||||
function getcontent(str) {
|
|
||||||
return unescape(substr(str, index(str, ":") + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
# AWK program
|
|
||||||
BEGIN { FS = ":"; regex = "^" field }
|
BEGIN { FS = ":"; regex = "^" field }
|
||||||
/^BEGIN:VEVENT$/ { inside = 1 }
|
/^BEGIN:VEVENT$/ { inside = 1 }
|
||||||
/^END:VEVENT$/ { exit }
|
/^END:VEVENT$/ { exit }
|
||||||
|
@ -3,40 +3,7 @@
|
|||||||
##
|
##
|
||||||
## @assign uid: UID to use
|
## @assign uid: UID to use
|
||||||
|
|
||||||
# Functions
|
@include "lib/awk/icalendar.awk"
|
||||||
|
|
||||||
# Escape string to be used as content in iCalendar files.
|
|
||||||
#
|
|
||||||
# @input str: String to escape
|
|
||||||
# @return: Escaped string
|
|
||||||
function escape(str)
|
|
||||||
{
|
|
||||||
gsub("\\\\", "\\\\", str)
|
|
||||||
gsub(";", "\\;", str)
|
|
||||||
gsub(",", "\\,", str)
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print property with its content and fold according to the iCalendar
|
|
||||||
# specification.
|
|
||||||
#
|
|
||||||
# @local variables: i, s
|
|
||||||
# @input nameparam: Property name with optional parameters
|
|
||||||
# @input content: Escaped content
|
|
||||||
function print_fold(nameparam, content, i, s)
|
|
||||||
{
|
|
||||||
i = 74 - length(nameparam)
|
|
||||||
s = substr(content, 1, i)
|
|
||||||
print nameparam s
|
|
||||||
s = substr(content, i+1, 73)
|
|
||||||
i = i + 73
|
|
||||||
while (s)
|
|
||||||
{
|
|
||||||
print " " s
|
|
||||||
s = substr(content, i+1, 73)
|
|
||||||
i = i + 73
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# AWK program
|
# AWK program
|
||||||
BEGIN {
|
BEGIN {
|
||||||
|
@ -6,79 +6,7 @@
|
|||||||
##
|
##
|
||||||
## @assign collection_labels: See configuration of the current program.
|
## @assign collection_labels: See configuration of the current program.
|
||||||
|
|
||||||
# Functions
|
@include "lib/awk/icalendar.awk"
|
||||||
|
|
||||||
# Unescape string
|
|
||||||
#
|
|
||||||
# @local variables: i, c, c2, res
|
|
||||||
# @input str: String
|
|
||||||
# @return: Unescaped string
|
|
||||||
function unescape(str, i, c, c2, res) {
|
|
||||||
for(i=1; i<=length(str);i++) {
|
|
||||||
c = substr(str, i, 1)
|
|
||||||
if (c != "\\") {
|
|
||||||
res = res c
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
c2 = substr(str, i, 1)
|
|
||||||
if (c2 == "n" || c2 == "N") {
|
|
||||||
res = res "\n"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
# Alternatively, c2 is "\\" or "," or ";". In each case, append res with
|
|
||||||
# c2. If the strings has been escaped correctly, then the character c2
|
|
||||||
# cannot be anything else. To be fail-safe, simply append res with c2.
|
|
||||||
res = res c2
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
# Isolate content part of an iCalendar line, and unescape.
|
|
||||||
#
|
|
||||||
# @input str: String
|
|
||||||
# @return: Unescaped content part
|
|
||||||
function getcontent(str) {
|
|
||||||
return unescape(substr(str, index(str, ":") + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
# Time-zone aware parsing of the date/date-time entry at the current record.
|
|
||||||
#
|
|
||||||
# @local variables: dt
|
|
||||||
# @return: date or date-time string that can be used in date (1)
|
|
||||||
function parse( dt) {
|
|
||||||
# Get timezone information
|
|
||||||
for (i=2; i<NF-1; i+=2) {
|
|
||||||
if ($i == "TZID") {
|
|
||||||
dt = "TZ=\"" $(i+1) "\" "
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Get date/date-time
|
|
||||||
return length($NF) == 8 ?
|
|
||||||
dt $NF :
|
|
||||||
dt gensub(/^([0-9]{8})T([0-9]{2})([0-9]{2})([0-9]{2})(Z)?$/, "\\1 \\2:\\3:\\4\\5", "g", $NF)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Map iCalendar duration specification into the format to be used in date (1).
|
|
||||||
#
|
|
||||||
# @local variables: dt, dta, i, n, a, seps
|
|
||||||
# @input duration: iCalendar duration string
|
|
||||||
# @return: relative-date/date-time specification to be used in date (1)
|
|
||||||
function parse_duration(duration, dt, dta, i, n, a, seps) {
|
|
||||||
n = split(duration, a, /[PTWHMSD]/, seps)
|
|
||||||
for (i=2; i<=n; i++) {
|
|
||||||
if(seps[i] == "W") dta["weeks"] = a[i]
|
|
||||||
if(seps[i] == "H") dta["hours"] = a[i]
|
|
||||||
if(seps[i] == "M") dta["minutes"] = a[i]
|
|
||||||
if(seps[i] == "S") dta["seconds"] = a[i]
|
|
||||||
if(seps[i] == "D") dta["days"] = a[i]
|
|
||||||
}
|
|
||||||
dt = a[1] ? a[1] : "+"
|
|
||||||
for (i in dta)
|
|
||||||
dt = dt " " dta[i] " " i
|
|
||||||
return dt
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print string of parsed data.
|
# Print string of parsed data.
|
||||||
#
|
#
|
||||||
|
@ -6,21 +6,7 @@
|
|||||||
##
|
##
|
||||||
## LIMITATION: This program does not fold long content lines.
|
## LIMITATION: This program does not fold long content lines.
|
||||||
|
|
||||||
# Functions
|
@include "lib/awk/icalendar.awk"
|
||||||
|
|
||||||
# Escape string to be used as content.
|
|
||||||
#
|
|
||||||
# @input str: Content string
|
|
||||||
# @return: Escaped string
|
|
||||||
function escape(str)
|
|
||||||
{
|
|
||||||
gsub("\\\\", "\\\\", str)
|
|
||||||
gsub(";", "\\;", str)
|
|
||||||
gsub(",", "\\,", str)
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
# AWK program
|
|
||||||
|
|
||||||
BEGIN { FS = "[:;]"; zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1) }
|
BEGIN { FS = "[:;]"; zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1) }
|
||||||
/^BEGIN:VEVENT$/ { inside = 1 }
|
/^BEGIN:VEVENT$/ { inside = 1 }
|
||||||
|
@ -1,42 +1,7 @@
|
|||||||
## src/awk/update.awk
|
## src/awk/update.awk
|
||||||
## Update iCalendar file from markdown file.
|
## Update iCalendar file from markdown file.
|
||||||
|
|
||||||
# Functions
|
@include "lib/awk/icalendar.awk"
|
||||||
|
|
||||||
# Escape string to be used as content in iCalendar files.
|
|
||||||
#
|
|
||||||
# @input str: String to escape
|
|
||||||
# @return: Escaped string
|
|
||||||
function escape(str)
|
|
||||||
{
|
|
||||||
gsub("\\\\", "\\\\", str)
|
|
||||||
gsub(";", "\\;", str)
|
|
||||||
gsub(",", "\\,", str)
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print property with its content and fold according to the iCalendar
|
|
||||||
# specification.
|
|
||||||
#
|
|
||||||
# @local variables: i, s
|
|
||||||
# @input nameparam: Property name with optional parameters
|
|
||||||
# @input content: Escaped content
|
|
||||||
function print_fold(nameparam, content, i, s)
|
|
||||||
{
|
|
||||||
i = 74 - length(nameparam)
|
|
||||||
s = substr(content, 1, i)
|
|
||||||
print nameparam s
|
|
||||||
s = substr(content, i+1, 73)
|
|
||||||
i = i + 73
|
|
||||||
while (s)
|
|
||||||
{
|
|
||||||
print " " s
|
|
||||||
s = substr(content, i+1, 73)
|
|
||||||
i = i + 73
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# AWK program
|
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
FS=":"
|
FS=":"
|
||||||
|
104
src/lib/awk/icalendar.awk
Normal file
104
src/lib/awk/icalendar.awk
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# Escape string to be used as content in iCalendar files.
|
||||||
|
#
|
||||||
|
# @input str: String to escape
|
||||||
|
# @return: Escaped string
|
||||||
|
function escape(str)
|
||||||
|
{
|
||||||
|
gsub("\\\\", "\\\\", str)
|
||||||
|
gsub(";", "\\;", str)
|
||||||
|
gsub(",", "\\,", str)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print property with its content and fold according to the iCalendar
|
||||||
|
# specification.
|
||||||
|
#
|
||||||
|
# @local variables: i, s
|
||||||
|
# @input nameparam: Property name with optional parameters
|
||||||
|
# @input content: Escaped content
|
||||||
|
function print_fold(nameparam, content, i, s)
|
||||||
|
{
|
||||||
|
i = 74 - length(nameparam)
|
||||||
|
s = substr(content, 1, i)
|
||||||
|
print nameparam s
|
||||||
|
s = substr(content, i+1, 73)
|
||||||
|
i = i + 73
|
||||||
|
while (s)
|
||||||
|
{
|
||||||
|
print " " s
|
||||||
|
s = substr(content, i+1, 73)
|
||||||
|
i = i + 73
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Unescape string
|
||||||
|
#
|
||||||
|
# @local variables: i, c, c2, res
|
||||||
|
# @input str: String
|
||||||
|
# @return: Unescaped string
|
||||||
|
function unescape(str, i, c, c2, res) {
|
||||||
|
for(i=1; i<=length(str);i++) {
|
||||||
|
c = substr(str, i, 1)
|
||||||
|
if (c != "\\") {
|
||||||
|
res = res c
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
c2 = substr(str, i, 1)
|
||||||
|
if (c2 == "n" || c2 == "N") {
|
||||||
|
res = res "\n"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
# Alternatively, c2 is "\\" or "," or ";". In each case, append res with
|
||||||
|
# c2. If the strings has been escaped correctly, then the character c2
|
||||||
|
# cannot be anything else. To be fail-safe, simply append res with c2.
|
||||||
|
res = res c2
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
# Isolate content part of an iCalendar line, and unescape.
|
||||||
|
#
|
||||||
|
# @input str: String
|
||||||
|
# @return: Unescaped content part
|
||||||
|
function getcontent(str) {
|
||||||
|
return unescape(substr(str, index(str, ":") + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Time-zone aware parsing of the date/date-time entry at the current record.
|
||||||
|
#
|
||||||
|
# @local variables: dt
|
||||||
|
# @return: date or date-time string that can be used in date (1)
|
||||||
|
function parse( dt) {
|
||||||
|
# Get timezone information
|
||||||
|
for (i=2; i<NF-1; i+=2) {
|
||||||
|
if ($i == "TZID") {
|
||||||
|
dt = "TZ=\"" $(i+1) "\" "
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Get date/date-time
|
||||||
|
return length($NF) == 8 ?
|
||||||
|
dt $NF :
|
||||||
|
dt gensub(/^([0-9]{8})T([0-9]{2})([0-9]{2})([0-9]{2})(Z)?$/, "\\1 \\2:\\3:\\4\\5", "g", $NF)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map iCalendar duration specification into the format to be used in date (1).
|
||||||
|
#
|
||||||
|
# @local variables: dt, dta, i, n, a, seps
|
||||||
|
# @input duration: iCalendar duration string
|
||||||
|
# @return: relative-date/date-time specification to be used in date (1)
|
||||||
|
function parse_duration(duration, dt, dta, i, n, a, seps) {
|
||||||
|
n = split(duration, a, /[PTWHMSD]/, seps)
|
||||||
|
for (i=2; i<=n; i++) {
|
||||||
|
if(seps[i] == "W") dta["weeks"] = a[i]
|
||||||
|
if(seps[i] == "H") dta["hours"] = a[i]
|
||||||
|
if(seps[i] == "M") dta["minutes"] = a[i]
|
||||||
|
if(seps[i] == "S") dta["seconds"] = a[i]
|
||||||
|
if(seps[i] == "D") dta["days"] = a[i]
|
||||||
|
}
|
||||||
|
dt = a[1] ? a[1] : "+"
|
||||||
|
for (i in dta)
|
||||||
|
dt = dt " " dta[i] " " i
|
||||||
|
return dt
|
||||||
|
}
|
1120
src/main.sh
1120
src/main.sh
File diff suppressed because it is too large
Load Diff
106
src/sh/awkscripts.sh
Normal file
106
src/sh/awkscripts.sh
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# AWK scripts
|
||||||
|
# - AWK_APPROX: Generate approximate data of all files
|
||||||
|
# - AWK_CALSHIFT: Shift calendar to start weeks on Mondays
|
||||||
|
# - AWK_CALANNOT: Annotate calendar
|
||||||
|
# - AWK_DAYVIEW: Generate view of the day
|
||||||
|
# - AWK_GET: Print field of iCalendar file
|
||||||
|
# - AWK_MERGE: Generate list of weeks with associated iCalendar files
|
||||||
|
# - AWK_NEW: Make new iCalendar file
|
||||||
|
# - AWK_PARSE: Timezone aware parsing of iCalendar file for day view
|
||||||
|
# - AWK_SET: Set value of specific field in iCalendar file
|
||||||
|
# - AWK_UPDATE: Update iCalendar file
|
||||||
|
# - AWK_WEEKVIEW: Generate view of the week
|
||||||
|
# - AWK_ATTACHLS: List attachments
|
||||||
|
# - AWK_ATTACHDD: Store attachment
|
||||||
|
# - AWK_ATTACHRM: Remove attachment
|
||||||
|
# - AWK_ATTACH: Add attachment
|
||||||
|
|
||||||
|
AWK_APPROX=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/approx.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_MERGE=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/merge.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_PARSE=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/parse.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_WEEKVIEW=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/weekview.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_DAYVIEW=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/dayview.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_GET=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/get.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_UPDATE=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/update.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_NEW=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/new.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_CALSHIFT=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/calshift.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_CALANNOT=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/calannot.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_SET=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/set.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_ATTACHLS=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/attachls.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_ATTACHDD=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/attachdd.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_ATTACHRM=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/attachrm.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
AWK_ATTACH=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include awk/attach.awk
|
||||||
|
EOF
|
||||||
|
)
|
129
src/sh/cliextra.sh
Normal file
129
src/sh/cliextra.sh
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Extra command-line options
|
||||||
|
# - --import-ni
|
||||||
|
# - --import
|
||||||
|
# - --git
|
||||||
|
# - --git-init
|
||||||
|
|
||||||
|
# Import iCalendar file noninteractively
|
||||||
|
#
|
||||||
|
# @input $2: Absolute path to iCalendar file
|
||||||
|
# @input $3: Collection
|
||||||
|
# @return: On success, returns 0, otherwise 1
|
||||||
|
if [ "${1:-}" = "--import-ni" ]; then
|
||||||
|
shift
|
||||||
|
file="${1:-}"
|
||||||
|
collection="${2:-}"
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
err "File \"$file\" does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for c in $(echo "$COLLECTION_LABELS" | sed "s|=[^;]*;| |g"); do
|
||||||
|
if [ "$collection" = "$c" ]; then
|
||||||
|
cexists="yes"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -n "${cexists:-}" ] && [ -d "$ROOT/$collection" ]; then
|
||||||
|
__import_to_collection "$file" "$collection"
|
||||||
|
else
|
||||||
|
err "Collection \"$collection\" does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Import iCalendar file.
|
||||||
|
#
|
||||||
|
# @input $2: Absolute path to iCalendar file
|
||||||
|
# @return: On success, returns 0, otherwise 1
|
||||||
|
if [ "${1:-}" = "--import" ]; then
|
||||||
|
shift
|
||||||
|
file="${1:-}"
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
err "File \"$file\" does not exist"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
line=$(awk \
|
||||||
|
-v collection_labels="$COLLECTION_LABELS" \
|
||||||
|
"$AWK_PARSE" "$file")
|
||||||
|
set -- $line
|
||||||
|
startsec="${1:-}"
|
||||||
|
endsec="${2:-}"
|
||||||
|
if [ -z "$line" ] || [ -z "$startsec" ] || [ -z "$endsec" ]; then
|
||||||
|
err "File \"$file\" does not look like an iCalendar file containing an event"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
start=$(__datetime_human_machine "$startsec")
|
||||||
|
end=$(__datetime_human_machine "$endsec")
|
||||||
|
location=$(awk -v field="LOCATION" "$AWK_GET" "$file")
|
||||||
|
summary=$(awk -v field="SUMMARY" "$AWK_GET" "$file")
|
||||||
|
description=$(awk -v field="DESCRIPTION" "$AWK_GET" "$file")
|
||||||
|
filetmp=$(mktemp --suffix='.md')
|
||||||
|
(
|
||||||
|
echo "::: |> $start"
|
||||||
|
echo "::: <| $end"
|
||||||
|
) >"$filetmp"
|
||||||
|
if [ -n "$location" ]; then
|
||||||
|
echo "@ $location" >>"$filetmp"
|
||||||
|
fi
|
||||||
|
(
|
||||||
|
echo "# $summary"
|
||||||
|
echo ""
|
||||||
|
echo "$description"
|
||||||
|
) >>"$filetmp"
|
||||||
|
$CAT "$filetmp" >/dev/tty
|
||||||
|
while true; do
|
||||||
|
printf "Do you want to import this entry? (yes/no): " >/dev/tty
|
||||||
|
read -r yn
|
||||||
|
case $yn in
|
||||||
|
"yes")
|
||||||
|
collection=$(echo "$COLLECTION_LABELS" | tr ';' '\n' | awk '/./ {print}' | $FZF --margin="30%" --no-info --delimiter='=' --with-nth=2 --accept-nth=1)
|
||||||
|
if [ -z "$collection" ]; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
__import_to_collection "$file" "$collection"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
"no")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Please answer \"yes\" or \"no\"." >/dev/tty
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
rm -f "$filetmp"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run git command
|
||||||
|
#
|
||||||
|
# @input $2..: Git command
|
||||||
|
# @return: On success, returns 0, otherwise 1
|
||||||
|
if [ "${1:-}" = "--git" ]; then
|
||||||
|
if [ -z "${GIT:-}" ]; then
|
||||||
|
err "Git not supported, run \`$0 --git-init\` first"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
$GIT "$@"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Enable the ues of git
|
||||||
|
#
|
||||||
|
# @return: On success, returns 0, otherwise 1
|
||||||
|
if [ "${1:-}" = "--git-init" ]; then
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
err "Git already enabled"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! command -v "git" >/dev/null; then
|
||||||
|
err "Git command not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
git -C "$ROOT" init
|
||||||
|
git -C "$ROOT" add -A
|
||||||
|
git -C "$ROOT" commit -m 'Initial commit: Start git tracking'
|
||||||
|
exit
|
||||||
|
fi
|
100
src/sh/clipreview.sh
Normal file
100
src/sh/clipreview.sh
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Preview command-line options
|
||||||
|
# - --preview-event
|
||||||
|
# - --preview_week
|
||||||
|
|
||||||
|
# Print preview of event and exit.
|
||||||
|
#
|
||||||
|
# @input $2: Line from day view containing an event
|
||||||
|
if [ "${1:-}" = "--preview-event" ]; then
|
||||||
|
hour=$(echo "$2" | cut -d '|' -f 2)
|
||||||
|
start=$(echo "$2" | cut -d '|' -f 3)
|
||||||
|
end=$(echo "$2" | cut -d '|' -f 4)
|
||||||
|
fpath=$(echo "$2" | cut -d '|' -f 5 | sed "s/ /|/g")
|
||||||
|
if [ -n "$hour" ] && [ -n "$fpath" ]; then
|
||||||
|
fpath="$ROOT/$fpath"
|
||||||
|
start=$(datetime_str "$start" "%a ")
|
||||||
|
end=$(datetime_str "$end" "%a ")
|
||||||
|
location=$(awk -v field="LOCATION" "$AWK_GET" "$fpath")
|
||||||
|
status=$(awk -v field="STATUS" "$AWK_GET" "$fpath")
|
||||||
|
if [ "$status" = "TENTATIVE" ]; then
|
||||||
|
symb="🟡"
|
||||||
|
elif [ "$status" = "CANCELLED" ]; then
|
||||||
|
symb="❌"
|
||||||
|
fi
|
||||||
|
echo "📅${symb:-} ${CYAN}$start${OFF} → ${CYAN}$end${OFF}"
|
||||||
|
if [ -n "${location:-}" ]; then
|
||||||
|
echo "📍 ${CYAN}$location${OFF}"
|
||||||
|
fi
|
||||||
|
attcnt=$(awk "$AWK_ATTACHLS" "$fpath" | wc -l)
|
||||||
|
if [ "$attcnt" -gt 0 ]; then
|
||||||
|
echo "🔗 $attcnt attachments"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
awk -v field="DESCRIPTION" "$AWK_GET" "$fpath" | $CAT
|
||||||
|
fi
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Print preview of week.
|
||||||
|
#
|
||||||
|
# @input $2: Line from week view
|
||||||
|
if [ "${1:-}" = "--preview-week" ]; then
|
||||||
|
sign=$(echo "$2" | cut -d '|' -f 1)
|
||||||
|
if [ "$sign" = "+" ]; then
|
||||||
|
startdate=$(echo "$2" | cut -d '|' -f 2)
|
||||||
|
set -- $(date -d "$startdate" +"%Y %m %d")
|
||||||
|
year=$1
|
||||||
|
month=$2
|
||||||
|
day=$3
|
||||||
|
set -- $(date -d "today" +"%Y %m %d")
|
||||||
|
year_cur=$1
|
||||||
|
month_cur=$2
|
||||||
|
day_cur=$3
|
||||||
|
# Previous months
|
||||||
|
set -- $(month_previous "$month" "$year")
|
||||||
|
month_pre="$1"
|
||||||
|
year_pre="$2"
|
||||||
|
set -- $(month_previous "$month_pre" "$year_pre")
|
||||||
|
month_pre2="$1"
|
||||||
|
year_pre2="$2"
|
||||||
|
# Next months
|
||||||
|
set -- $(month_next "$month" "$year")
|
||||||
|
month_nex="$1"
|
||||||
|
year_nex="$2"
|
||||||
|
set -- $(month_next "$month_nex" "$year_nex")
|
||||||
|
month_nex2="$1"
|
||||||
|
year_nex2="$2"
|
||||||
|
set -- $(month_next "$month_nex2" "$year_nex2")
|
||||||
|
month_nex3="$1"
|
||||||
|
year_nex3="$2"
|
||||||
|
# Highlight today
|
||||||
|
if [ "$month_pre2" -eq "$month_cur" ] && [ "$year_pre2" -eq "$year_cur" ]; then
|
||||||
|
var_pre2=$day_cur
|
||||||
|
fi
|
||||||
|
if [ "$month_pre" -eq "$month_cur" ] && [ "$year_pre" -eq "$year_cur" ]; then
|
||||||
|
var_pre=$day_cur
|
||||||
|
fi
|
||||||
|
if [ "$month" -eq "$month_cur" ] && [ "$year" -eq "$year_cur" ]; then
|
||||||
|
var=$day_cur
|
||||||
|
fi
|
||||||
|
if [ "$month_nex" -eq "$month_cur" ] && [ "$year_nex" -eq "$year_cur" ]; then
|
||||||
|
var_nex=$day_cur
|
||||||
|
fi
|
||||||
|
if [ "$month_nex2" -eq "$month_cur" ] && [ "$year_nex2" -eq "$year_cur" ]; then
|
||||||
|
var_nex2=$day_cur
|
||||||
|
fi
|
||||||
|
if [ "$month_nex3" -eq "$month_cur" ] && [ "$year_nex3" -eq "$year_cur" ]; then
|
||||||
|
var_nex3=$day_cur
|
||||||
|
fi
|
||||||
|
# show
|
||||||
|
(
|
||||||
|
cal "$month_pre2" "$year_pre2" | awk "$AWK_CALSHIFT" | awk -v cur="${var_pre2:-}" "$AWK_CALANNOT"
|
||||||
|
cal "$month_pre" "$year_pre" | awk "$AWK_CALSHIFT" | awk -v cur="${var_pre:-}" "$AWK_CALANNOT"
|
||||||
|
cal "$month" "$year" | awk "$AWK_CALSHIFT" | awk -v cur="${var:-}" -v day="$day" "$AWK_CALANNOT"
|
||||||
|
cal "$month_nex" "$year_nex" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex:-}" "$AWK_CALANNOT"
|
||||||
|
cal "$month_nex2" "$year_nex2" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex2:-}" "$AWK_CALANNOT"
|
||||||
|
cal "$month_nex3" "$year_nex3" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex3:-}" "$AWK_CALANNOT"
|
||||||
|
) | awk '{ l[NR%8] = l[NR%8] " " $0 } END {for (i in l) if (i>0) print l[i] }'
|
||||||
|
fi
|
||||||
|
exit
|
||||||
|
fi
|
31
src/sh/clireload.sh
Normal file
31
src/sh/clireload.sh
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Command-line Arguments for reloading views
|
||||||
|
# - --reload-day
|
||||||
|
# - --reload-week
|
||||||
|
# - --reload-all
|
||||||
|
|
||||||
|
# Reload view of specified day.
|
||||||
|
#
|
||||||
|
# @input $2.. (optional): Specification of day, defaults to `today`
|
||||||
|
if [ "${1:-}" = "--reload-day" ]; then
|
||||||
|
shift
|
||||||
|
DISPLAY_DATE=${*:-today}
|
||||||
|
__view_day
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reload view of the week containing the specified date.
|
||||||
|
#
|
||||||
|
# @input $2.. (optional): Specification of day, defaults to `today`
|
||||||
|
if [ "${1:-}" = "--reload-week" ]; then
|
||||||
|
shift
|
||||||
|
DISPLAY_DATE=${*:-today}
|
||||||
|
DISPLAY_POS=$((8 - $(date -d "$DISPLAY_DATE" +"%u")))
|
||||||
|
__view_week
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reload view of all entries.
|
||||||
|
if [ "${1:-}" = "--reload-all" ]; then
|
||||||
|
__view_all
|
||||||
|
exit
|
||||||
|
fi
|
65
src/sh/config.sh
Normal file
65
src/sh/config.sh
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Load Configuration
|
||||||
|
# - ROOT: Directory containing the collections
|
||||||
|
# - COLLECTION_LABELS: Mappings between collections and labels
|
||||||
|
# - SYNC_CMD (optional): Synchronization command
|
||||||
|
# - DAY_START (optional): Hour of start of the day (defaults to 8)
|
||||||
|
# - DAY_END (optional): Hour of end of the day (defaults to 18)
|
||||||
|
# - EDITOR (optional): Your favorite editor, is usually already exported
|
||||||
|
# - TZ (optional): Your favorite timezone, usually system's choice
|
||||||
|
# - LC_TIME (optional): Your favorite locale for date and time
|
||||||
|
# - ZI_DIR (optional): Location of tzdata, defaults to /usr/share/zoneinfo
|
||||||
|
|
||||||
|
CONFIGFILE="$HOME/.config/fzf-vcal/config"
|
||||||
|
if [ ! -f "$CONFIGFILE" ]; then
|
||||||
|
err "Configuration '$CONFIGFILE' not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$CONFIGFILE"
|
||||||
|
if [ -z "${ROOT:-}" ] || [ -z "${COLLECTION_LABELS:-}" ]; then
|
||||||
|
err "Configuration is incomplete."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SYNC_CMD=${SYNC_CMD:-echo 'Synchronization disabled'}
|
||||||
|
DAY_START=${DAY_START:-8}
|
||||||
|
DAY_END=${DAY_END:-18}
|
||||||
|
ZI_DIR=${ZI_DIR:-/usr/share/zoneinfo/posix}
|
||||||
|
if [ ! -d "$ZI_DIR" ]; then
|
||||||
|
err "Could not determine time-zone information"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
OPEN=${OPEN:-open}
|
||||||
|
|
||||||
|
# Check and load required tools
|
||||||
|
# - FZF: Fuzzy finder `fzf``
|
||||||
|
# - UUIDGEN: Tool `uuidgen` to generate random uids
|
||||||
|
# - CAT: `bat` or `batcat` or `cat`
|
||||||
|
# - GIT: `git` if it exists
|
||||||
|
#
|
||||||
|
# The presence of POSIX tools is not checked.
|
||||||
|
|
||||||
|
if command -v "fzf" >/dev/null; then
|
||||||
|
FZF="fzf --black"
|
||||||
|
else
|
||||||
|
err "Did not find the command-line fuzzy finder fzf."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v "uuidgen" >/dev/null; then
|
||||||
|
UUIDGEN="uuidgen"
|
||||||
|
else
|
||||||
|
err "Did not find the uuidgen command."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v "bat" >/dev/null; then
|
||||||
|
CAT="bat"
|
||||||
|
elif command -v "batcat" >/dev/null; then
|
||||||
|
CAT="batcat"
|
||||||
|
fi
|
||||||
|
CAT=${CAT:+$CAT --color=always --style=numbers --language=md}
|
||||||
|
CAT=${CAT:-cat}
|
||||||
|
|
||||||
|
if command -v "git" >/dev/null && [ -d "$ROOT/.git" ]; then
|
||||||
|
GIT="git -C $ROOT"
|
||||||
|
fi
|
256
src/sh/icalendar.sh
Normal file
256
src/sh/icalendar.sh
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
# iCalendar modification wrapper
|
||||||
|
# - __edit
|
||||||
|
# - __new
|
||||||
|
# - __delete
|
||||||
|
# - __import_to_collection
|
||||||
|
# - __cancel_toggle
|
||||||
|
# - __tentative_toggle
|
||||||
|
# - __add_attachment
|
||||||
|
|
||||||
|
# Edit iCalendar file.
|
||||||
|
#
|
||||||
|
# @input $1: Start date/date-time
|
||||||
|
# @input $2: End date/date-time
|
||||||
|
# @input $3: Path to iCalendar file (relative to `$ROOT`)
|
||||||
|
# @req $AWK_GET: Awk script to extract fields from iCalendar file
|
||||||
|
# @req $AWK_UPDATE: Awk script to update iCalendar file
|
||||||
|
# @req $EDITOR: Environment variable of your favorite editor
|
||||||
|
__edit() {
|
||||||
|
start=$(__datetime_human_machine "$1")
|
||||||
|
end=$(__datetime_human_machine "$2")
|
||||||
|
fpath="$ROOT/$3"
|
||||||
|
location=$(awk -v field="LOCATION" "$AWK_GET" "$fpath")
|
||||||
|
summary=$(awk -v field="SUMMARY" "$AWK_GET" "$fpath")
|
||||||
|
description=$(awk -v field="DESCRIPTION" "$AWK_GET" "$fpath")
|
||||||
|
filetmp=$(mktemp --suffix='.md')
|
||||||
|
printf "::: |> %s\n::: <| %s\n" "$start" "$end" >"$filetmp"
|
||||||
|
if [ -n "$location" ]; then
|
||||||
|
printf "@ %s\n" "$location" >>"$filetmp"
|
||||||
|
fi
|
||||||
|
printf "# %s\n\n%s\n" "$summary" "$description" >>"$filetmp"
|
||||||
|
checksum=$(cksum "$filetmp")
|
||||||
|
$EDITOR "$filetmp" >/dev/tty
|
||||||
|
|
||||||
|
# Update only if changes are detected
|
||||||
|
if [ "$checksum" != "$(cksum "$filetmp")" ]; then
|
||||||
|
filenew="$filetmp.ics"
|
||||||
|
if awk "$AWK_UPDATE" "$filetmp" "$fpath" >"$filenew"; then
|
||||||
|
mv "$filenew" "$fpath"
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
$GIT add "$fpath"
|
||||||
|
$GIT commit -m "Modified event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
|
||||||
|
fi
|
||||||
|
__refresh_data
|
||||||
|
else
|
||||||
|
rm -f "$filenew"
|
||||||
|
err "Failed to edit entry. Press <enter> to continue."
|
||||||
|
read -r tmp
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm "$filetmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate new iCalendar file
|
||||||
|
#
|
||||||
|
# This function also sets the `$start` variable to the start of the new entry.
|
||||||
|
# On failure, start will be empty.
|
||||||
|
#
|
||||||
|
# If some start has been specified and the nanoseconds are not 0, we assume
|
||||||
|
# that the user entered "tomorrow" or something like that, and did not
|
||||||
|
# specify the time. So, we will use the `$DAY_START` time of that date.
|
||||||
|
# If the user specified a malformed date/date-time, we fail.
|
||||||
|
#
|
||||||
|
# @input $1 (optional): Date or datetime, defaults to today.
|
||||||
|
# @req $COLLECTION_LABELS: Mapping between collections and lables (see configuration)
|
||||||
|
# @req $UUIDGEN: `uuidgen` command
|
||||||
|
# @req $ROOT: Path that contains the collections (see configuration)
|
||||||
|
# @req $EDITOR: Environment variable of your favorite editor
|
||||||
|
# @req $AWK_GET: Awk script to extract fields from iCalendar file
|
||||||
|
# @req $AWK_new: Awk script to generate iCalendar file
|
||||||
|
__new() {
|
||||||
|
collection=$(echo "$COLLECTION_LABELS" | tr ';' '\n' | awk '/./ {print}' | $FZF --margin="30%" --no-info --delimiter='=' --with-nth=2 --accept-nth=1)
|
||||||
|
fpath=""
|
||||||
|
while [ -f "$fpath" ] || [ -z "$fpath" ]; do
|
||||||
|
uuid=$($UUIDGEN)
|
||||||
|
fpath="$ROOT/$collection/$uuid.ics"
|
||||||
|
done
|
||||||
|
d="today $DAY_START"
|
||||||
|
if [ -n "${1:-}" ]; then
|
||||||
|
d="$1"
|
||||||
|
if [ "$(date -d "$1" +"%N")" -ne 0 ]; then
|
||||||
|
d="$d $DAY_START:00"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
startsec=$(date -d "$d" +"%s")
|
||||||
|
endsec=$((startsec + 3600))
|
||||||
|
start=$(__datetime_human_machine "$startsec")
|
||||||
|
end=$(__datetime_human_machine "$endsec")
|
||||||
|
filetmp=$(mktemp --suffix='.md')
|
||||||
|
(
|
||||||
|
echo "::: |> $start"
|
||||||
|
echo "::: <| $end"
|
||||||
|
echo "@ <!-- write location here, optional line -->"
|
||||||
|
echo "# <!-- write summary here -->"
|
||||||
|
echo ""
|
||||||
|
) >"$filetmp"
|
||||||
|
checksum=$(cksum "$filetmp")
|
||||||
|
$EDITOR "$filetmp" >/dev/tty
|
||||||
|
|
||||||
|
# Update only if changes are detected
|
||||||
|
if [ "$checksum" != "$(cksum "$filetmp")" ]; then
|
||||||
|
filenew="$filetmp.ics"
|
||||||
|
if awk -v uid="$uuid" "$AWK_NEW" "$filetmp" >"$filenew"; then
|
||||||
|
mv "$filenew" "$fpath"
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
$GIT add "$fpath"
|
||||||
|
$GIT commit -m "Added event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
|
||||||
|
fi
|
||||||
|
start=$(awk -v field="DTSTART" "$AWK_GET" "$fpath" | grep -o '[0-9]\{8\}')
|
||||||
|
else
|
||||||
|
rm -f "$filenew"
|
||||||
|
start=""
|
||||||
|
err "Failed to create new entry. Press <enter> to continue."
|
||||||
|
read -r tmp
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm "$filetmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete iCalendar file
|
||||||
|
#
|
||||||
|
# @input $1: Path to iCalendar file, relative to `$ROOT`
|
||||||
|
# @req $ROOT: Path that contains the collections (see configuration)
|
||||||
|
# @req $AWK_GET: Awk script to extract fields from iCalendar file
|
||||||
|
__delete() {
|
||||||
|
fpath="$ROOT/$1"
|
||||||
|
summary=$(awk -v field="SUMMARY" "$AWK_GET" "$fpath")
|
||||||
|
while true; do
|
||||||
|
printf "Do you want to delete the entry with the title \"%s\"? (yes/no): " "$summary" >/dev/tty
|
||||||
|
read -r yn
|
||||||
|
case $yn in
|
||||||
|
"yes")
|
||||||
|
sfg="$(__summary_for_commit "$fpath")"
|
||||||
|
rm -v "$fpath"
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
$GIT add "$fpath"
|
||||||
|
$GIT commit -m "Deleted event '$sfg ...'" -- "$fpath"
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
"no")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Please answer \"yes\" or \"no\"." >/dev/tty
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Import iCalendar file to specified collection. The only modification made to
|
||||||
|
# the file is setting the UID.
|
||||||
|
#
|
||||||
|
# @input $1: path to iCalendar file
|
||||||
|
# @input $2: collection name
|
||||||
|
# @req $ROOT: Path that contains the collections (see configuration)
|
||||||
|
# @req $UUIDGEN: `uuidgen` command
|
||||||
|
# @req $AWK_SET: Awk script to set field value
|
||||||
|
__import_to_collection() {
|
||||||
|
file="$1"
|
||||||
|
collection="$2"
|
||||||
|
fpath=""
|
||||||
|
while [ -f "$fpath" ] || [ -z "$fpath" ]; do
|
||||||
|
uuid=$($UUIDGEN)
|
||||||
|
fpath="$ROOT/$collection/$uuid.ics"
|
||||||
|
done
|
||||||
|
filetmp=$(mktemp)
|
||||||
|
awk -v field="UID" -v value="$uuid" "$AWK_SET" "$file" >"$filetmp"
|
||||||
|
mv "$filetmp" "$fpath"
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
$GIT add "$fpath"
|
||||||
|
$GIT commit -m "Imported event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set status of appointment to CANCELLED or CONFIRMED (toggle)
|
||||||
|
#
|
||||||
|
# @input $1: path to iCalendar file
|
||||||
|
# @req $ROOT: Path that contains the collections (see configuration)
|
||||||
|
# @req $AWK_SET: Awk script to set field value
|
||||||
|
# @req $AWK_GET: Awk script to extract fields from iCalendar file
|
||||||
|
__cancel_toggle() {
|
||||||
|
fpath="$ROOT/$1"
|
||||||
|
status=$(awk -v field="STATUS" "$AWK_GET" "$fpath")
|
||||||
|
newstatus="CANCELLED"
|
||||||
|
if [ "${status:-}" = "$newstatus" ]; then
|
||||||
|
newstatus="CONFIRMED"
|
||||||
|
fi
|
||||||
|
filetmp=$(mktemp)
|
||||||
|
awk -v field="STATUS" -v value="$newstatus" "$AWK_SET" "$fpath" >"$filetmp"
|
||||||
|
mv "$filetmp" "$fpath"
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
$GIT add "$fpath"
|
||||||
|
$GIT commit -m "Event '$(__summary_for_commit "$fpath") ...' has now status $status" -- "$fpath"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Toggle status flag: CONFIRMED <-> TENTATIVE
|
||||||
|
#
|
||||||
|
# @input $1: path to iCalendar file
|
||||||
|
# @req $ROOT: Path that contains the collections (see configuration)
|
||||||
|
# @req $AWK_SET: Awk script to set field value
|
||||||
|
# @req $AWK_GET: Awk script to extract fields from iCalendar file
|
||||||
|
__tentative_toggle() {
|
||||||
|
fpath="$ROOT/$1"
|
||||||
|
status=$(awk -v field="STATUS" "$AWK_GET" "$fpath")
|
||||||
|
newstatus="TENTATIVE"
|
||||||
|
if [ "${status:-}" = "$newstatus" ]; then
|
||||||
|
newstatus="CONFIRMED"
|
||||||
|
fi
|
||||||
|
filetmp=$(mktemp)
|
||||||
|
awk -v field="STATUS" -v value="$newstatus" "$AWK_SET" "$fpath" >"$filetmp"
|
||||||
|
mv "$filetmp" "$fpath"
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
$GIT add "$fpath"
|
||||||
|
$GIT commit -m "Event '$(__summary_for_commit "$fpath") ...' has now status $status" -- "$fpath"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepend attachment to iCalendar file
|
||||||
|
#
|
||||||
|
# @input $1: path to iCalendar file
|
||||||
|
# @req $ROOT: Path that contains the collections (see configuration)
|
||||||
|
# @req $FZF: Fuzzy finder
|
||||||
|
# @req $AWK_ATTACH: Awk script to add attachment
|
||||||
|
__add_attachment() {
|
||||||
|
fpath="$ROOT/$1"
|
||||||
|
sel=$(
|
||||||
|
$FZF --prompt="Select attachment> " \
|
||||||
|
--walker="file,hidden" \
|
||||||
|
--walker-root="$HOME" \
|
||||||
|
--expect="ctrl-c,ctrl-g,ctrl-q,esc"
|
||||||
|
)
|
||||||
|
key=$(echo "$sel" | head -1)
|
||||||
|
f=$(echo "$sel" | tail -1)
|
||||||
|
if [ -n "$key" ]; then
|
||||||
|
f=""
|
||||||
|
fi
|
||||||
|
if [ -z "$f" ] || [ ! -f "$f" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
filename=$(basename "$f")
|
||||||
|
mime=$(file -b -i "$f" | cut -d ';' -f 1)
|
||||||
|
if [ -z "$mime" ]; then
|
||||||
|
mime="application/octet-stream"
|
||||||
|
fi
|
||||||
|
fenc=$(mktemp)
|
||||||
|
base64 "$f" >"$fenc"
|
||||||
|
filetmp=$(mktemp)
|
||||||
|
awk -v file="$fenc" -v mime="$mime" -v filename="$filename" "$AWK_ATTACH" "$fpath" >"$filetmp"
|
||||||
|
mv "$filetmp" "$fpath"
|
||||||
|
if [ -n "${GIT:-}" ]; then
|
||||||
|
$GIT add "$fpath"
|
||||||
|
$GIT commit -m "Added attachment to '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
|
||||||
|
fi
|
||||||
|
rm "$fenc"
|
||||||
|
}
|
41
src/sh/load.sh
Normal file
41
src/sh/load.sh
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Loading functions
|
||||||
|
# - __load_approx_data
|
||||||
|
# - __load_weeks
|
||||||
|
# - __refresh_data
|
||||||
|
|
||||||
|
# Print approximate data from iCalendar files in `$ROOT`
|
||||||
|
__load_approx_data() {
|
||||||
|
find "$ROOT" -type f -name '*.ics' -print0 |
|
||||||
|
xargs -0 -P0 \
|
||||||
|
awk \
|
||||||
|
-v collection_labels="$COLLECTION_LABELS" \
|
||||||
|
"$AWK_APPROX"
|
||||||
|
}
|
||||||
|
|
||||||
|
# For every relevant week, print associated iCalendar files
|
||||||
|
__load_weeks() {
|
||||||
|
dates=$(awk -F'|' '{ print $2; print $3 }' "$APPROX_DATA_FILE")
|
||||||
|
file_dates=$(mktemp)
|
||||||
|
echo "$dates" | date --file="/dev/stdin" +"%G|%V" >"$file_dates"
|
||||||
|
awk "$AWK_MERGE" "$file_dates" "$APPROX_DATA_FILE"
|
||||||
|
rm "$file_dates"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Refresh approximate data and per-week data.
|
||||||
|
#
|
||||||
|
# This functions stores the output of `__load_approx_data` in the temporary
|
||||||
|
# file `$APPROX_DATA_FILE` and the output of `__load_weeks` in the temporary
|
||||||
|
# file `@WEEKLY_DATA_FILE`.
|
||||||
|
__refresh_data() {
|
||||||
|
if [ -n "${APPROX_DATA_FILE:-}" ]; then
|
||||||
|
rm -f "$APPROX_DATA_FILE"
|
||||||
|
fi
|
||||||
|
if [ -n "${WEEKLY_DATA_FILE:-}" ]; then
|
||||||
|
rm -f "$WEEKLY_DATA_FILE"
|
||||||
|
fi
|
||||||
|
APPROX_DATA_FILE=$(mktemp)
|
||||||
|
__load_approx_data >"$APPROX_DATA_FILE"
|
||||||
|
WEEKLY_DATA_FILE=$(mktemp)
|
||||||
|
__load_weeks >"$WEEKLY_DATA_FILE"
|
||||||
|
trap 'rm -f "$APPROX_DATA_FILE" "$WEEKLY_DATA_FILE"' EXIT INT
|
||||||
|
}
|
27
src/sh/misc.sh
Normal file
27
src/sh/misc.sh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# err()
|
||||||
|
# This is a helper function to print errors.
|
||||||
|
#
|
||||||
|
# @input $1: Error message
|
||||||
|
err() {
|
||||||
|
echo "❌ $1" >/dev/tty
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print date or datetime in a human and machine readable form.
|
||||||
|
#
|
||||||
|
# @input $1: Seconds since epoch
|
||||||
|
__datetime_human_machine() {
|
||||||
|
s="$1"
|
||||||
|
t=$(date -d "@$s" +"%R")
|
||||||
|
dfmt="%F"
|
||||||
|
if [ "$t" != "00:00" ]; then
|
||||||
|
dfmt="$dfmt %R"
|
||||||
|
fi
|
||||||
|
date -d "@$s" +"$dfmt"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get summary string that can be used in for git-commit messages.
|
||||||
|
#
|
||||||
|
# @input $1: iCalendar file path
|
||||||
|
__summary_for_commit() {
|
||||||
|
awk -v field="SUMMARY" "$AWK_GET" "$1" | tr -c -d "[:alnum:][:blank:]" | head -c 15
|
||||||
|
}
|
51
src/sh/preview.sh
Normal file
51
src/sh/preview.sh
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Preview helper functions
|
||||||
|
# - month_previous
|
||||||
|
# - month_next
|
||||||
|
# - datetime_str
|
||||||
|
|
||||||
|
# Print previous month of specified input month as <month> <year>.
|
||||||
|
#
|
||||||
|
# @input $1: Month
|
||||||
|
# @input $2: Year
|
||||||
|
month_previous() {
|
||||||
|
month=$(echo "$1" | sed 's/^0//')
|
||||||
|
year=$(echo "$2" | sed 's/^0//')
|
||||||
|
if [ "$month" -eq 1 ]; then
|
||||||
|
month=12
|
||||||
|
year=$((year - 1))
|
||||||
|
else
|
||||||
|
month=$((month - 1))
|
||||||
|
fi
|
||||||
|
echo "$month $year"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print next month of specified input month as <month> <year>.
|
||||||
|
#
|
||||||
|
# @input $1: Month
|
||||||
|
# @input $2: Year
|
||||||
|
month_next() {
|
||||||
|
month=$(echo "$1" | sed 's/^0//')
|
||||||
|
year=$(echo "$2" | sed 's/^0//')
|
||||||
|
if [ "$month" -eq 12 ]; then
|
||||||
|
month=1
|
||||||
|
year=$((year + 1))
|
||||||
|
else
|
||||||
|
month=$((month + 1))
|
||||||
|
fi
|
||||||
|
echo "$month $year"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print date or datetime in a human readable form.
|
||||||
|
#
|
||||||
|
# @input $1: Seconds since epoch
|
||||||
|
# @input $2.. (optoinal): Prepend date format
|
||||||
|
datetime_str() {
|
||||||
|
s="$1"
|
||||||
|
shift
|
||||||
|
t=$(date -d "@$s" +"%R")
|
||||||
|
dfmt="$*%e %b %Y"
|
||||||
|
if [ "$t" != "00:00" ]; then
|
||||||
|
dfmt="$dfmt %R %Z"
|
||||||
|
fi
|
||||||
|
date -d "@$s" +"$dfmt"
|
||||||
|
}
|
8
src/sh/theme.sh
Normal file
8
src/sh/theme.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
GREEN="\033[1;32m"
|
||||||
|
RED="\033[1;31m"
|
||||||
|
WHITE="\033[1;97m"
|
||||||
|
CYAN="\033[1;36m"
|
||||||
|
STRIKE="\033[9m"
|
||||||
|
ITALIC="\033[3m"
|
||||||
|
FAINT="\033[2m"
|
||||||
|
OFF="\033[m"
|
130
src/sh/view.sh
Normal file
130
src/sh/view.sh
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# View Functions
|
||||||
|
# - __view_day
|
||||||
|
# - __view_week
|
||||||
|
# - __view_all
|
||||||
|
|
||||||
|
# This function prints the view for the day specified in `$DISPLAY_DATE`.
|
||||||
|
__view_day() {
|
||||||
|
weeknr=$(date -d "$DISPLAY_DATE" +"%G.%V")
|
||||||
|
files=$(grep "^$weeknr\ " "$WEEKLY_DATA_FILE" | cut -d " " -f 2-)
|
||||||
|
# Find relevant files in list of week files
|
||||||
|
sef=$({
|
||||||
|
set -- $files
|
||||||
|
for file in "$@"; do
|
||||||
|
file="$ROOT/$file"
|
||||||
|
awk \
|
||||||
|
-v collection_labels="$COLLECTION_LABELS" \
|
||||||
|
"$AWK_PARSE" "$file"
|
||||||
|
done
|
||||||
|
})
|
||||||
|
today=$(date -d "$DISPLAY_DATE" +"%D")
|
||||||
|
if [ -n "$sef" ]; then
|
||||||
|
sef=$(echo "$sef" | while IFS= read -r line; do
|
||||||
|
set -- $line
|
||||||
|
starttime="$1"
|
||||||
|
shift
|
||||||
|
endtime="$1"
|
||||||
|
shift
|
||||||
|
fpath="$(echo "$1" | sed 's/|/ /g')" # we will use | as delimiter (need to convert back!)
|
||||||
|
shift
|
||||||
|
collection="$1"
|
||||||
|
shift
|
||||||
|
status="$1"
|
||||||
|
shift
|
||||||
|
description="$(echo "$*" | sed 's/|/:/g')" # we will use | as delimiter
|
||||||
|
#
|
||||||
|
daystart=$(date -d "$today 00:00:00" +"%s")
|
||||||
|
dayend=$(date -d "$today 23:59:59" +"%s")
|
||||||
|
line=""
|
||||||
|
if [ "$starttime" -gt "$daystart" ] && [ "$starttime" -lt "$dayend" ]; then
|
||||||
|
s=$(date -d "@$starttime" +"%R")
|
||||||
|
elif [ "$starttime" -le "$daystart" ] && [ "$endtime" -gt "$daystart" ]; then
|
||||||
|
s="00:00"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$endtime" -gt "$daystart" ] && [ "$endtime" -lt "$dayend" ]; then
|
||||||
|
e=$(date -d "@$endtime" +"%R")
|
||||||
|
elif [ "$endtime" -ge "$dayend" ] && [ "$starttime" -lt "$dayend" ]; then
|
||||||
|
e="00:00"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "$s|$e|$starttime|$endtime|$fpath|$collection|$description|$status"
|
||||||
|
done)
|
||||||
|
fi
|
||||||
|
echo "$sef" | sort -n | awk -v today="$today" -v daystart="$DAY_START" -v dayend="$DAY_END" "$AWK_DAYVIEW"
|
||||||
|
}
|
||||||
|
|
||||||
|
# This function prints the view for the week that contains the day specified in `$DISPLAY_DATE`.
|
||||||
|
__view_week() {
|
||||||
|
weeknr=$(date -d "$DISPLAY_DATE" +"%G.%V")
|
||||||
|
files=$(grep "^$weeknr\ " "$WEEKLY_DATA_FILE" | cut -d " " -f 2-)
|
||||||
|
dayofweek=$(date -d "$DISPLAY_DATE" +"%u")
|
||||||
|
delta=$((1 - dayofweek))
|
||||||
|
startofweek=$(date -d "$DISPLAY_DATE -$delta days" +"%D")
|
||||||
|
# loop over files
|
||||||
|
sef=$({
|
||||||
|
set -- $files
|
||||||
|
for file in "$@"; do
|
||||||
|
file="$ROOT/$file"
|
||||||
|
awk \
|
||||||
|
-v collection_labels="$COLLECTION_LABELS" \
|
||||||
|
"$AWK_PARSE" "$file"
|
||||||
|
done
|
||||||
|
})
|
||||||
|
if [ -n "$sef" ]; then
|
||||||
|
sef=$(echo "$sef" | while IFS= read -r line; do
|
||||||
|
set -- $line
|
||||||
|
starttime="$1"
|
||||||
|
shift
|
||||||
|
endtime="$1"
|
||||||
|
shift
|
||||||
|
#fpath="$1"
|
||||||
|
shift
|
||||||
|
collection="$1"
|
||||||
|
shift
|
||||||
|
status="$1"
|
||||||
|
shift
|
||||||
|
if [ "$status" = "TENTATIVE" ]; then
|
||||||
|
symb="$FAINT$CYAN"
|
||||||
|
elif [ "$status" = "CANCELLED" ]; then
|
||||||
|
symb="$STRIKE"
|
||||||
|
else
|
||||||
|
symb=""
|
||||||
|
fi
|
||||||
|
description="${symb:-}$*$OFF"
|
||||||
|
for i in $(seq 0 7); do
|
||||||
|
daystart=$(date -d "$startofweek +$i days 00:00:00" +"%s")
|
||||||
|
dayend=$(date -d "$startofweek +$i days 23:59:59" +"%s")
|
||||||
|
if [ "$starttime" -gt "$daystart" ] && [ "$starttime" -lt "$dayend" ]; then
|
||||||
|
s=$(date -d "@$starttime" +"%H:%M")
|
||||||
|
s="$s -"
|
||||||
|
elif [ "$starttime" -le "$daystart" ] && [ "$endtime" -gt "$daystart" ]; then
|
||||||
|
s="00:00 -"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$endtime" -gt "$daystart" ] && [ "$endtime" -lt "$dayend" ]; then
|
||||||
|
e=$(date -d "@$endtime" +"%H:%M")
|
||||||
|
e="- $e"
|
||||||
|
elif [ "$endtime" -ge "$dayend" ] && [ "$starttime" -lt "$dayend" ]; then
|
||||||
|
e="- 00:00"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "$i $s$e >$description"
|
||||||
|
done
|
||||||
|
done)
|
||||||
|
fi
|
||||||
|
sef=$({
|
||||||
|
echo "$sef"
|
||||||
|
seq 0 7
|
||||||
|
} | sort -n)
|
||||||
|
echo "$sef" | awk -v startofweek="$startofweek" "$AWK_WEEKVIEW"
|
||||||
|
}
|
||||||
|
|
||||||
|
# This function prints all entries.
|
||||||
|
__view_all() {
|
||||||
|
cat "$APPROX_DATA_FILE"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user