This commit is contained in:
Jaidyn Ann 2020-05-03 16:11:00 -05:00
commit f17739dc92
11 changed files with 938 additions and 0 deletions

51
README.txt Normal file
View File

@ -0,0 +1,51 @@
VCARD2PERSON (and PERSON2VCARD)
===============================================================================
Some bash scripts that can convert in-between vcard and Haiku person files.
Sorta in the spirit of mbox2mail and mail2mbox.
It supports vcards of version 2.1, 3.0, and 4.0.— and supports all attributes
applicable to a person file (including photo [through URL or embeded image],
address, name, phone numbers, email, etc).
My use-case is converting contacts between my Android phone and Haiku, which
uses version 2.1 vcards, so that version's been tested fairly well.
3.0 and 4.0 have only been tested on the example files in ./ex/
--------------------
USAGE
--------------------
usage: vcard2person vcard [-h] [-d directory] [-o path] file
People files will be created using the data from a vcard. Each vcard defined
by a given vcf/vcard file have its person file be named after the vcard's
full-name.
-o changes the output person filename
-d outputs all people files to a given directory-- useful if a file
defines multiple vcards.
-h prints this message.
usage: person2vcard [-h] [-v version] [-o path] person
Creates a vcard from the given person file's attributes. By default will
output to a file of the same name with a .vcf file-extension.
-o changes output vcard filename
-v specify vcard version: 2.1, 3.0, or 4.0. Defaults to 4.0.
a random URL, and use it as the vcard's photo URL.
-h print this message.
--------------------
CUSTOMIZATION
--------------------
It should be pretty easy to work with your own custom attributes-- a couple
examples are in './patches/', for a META:birthday attribute (BDAY) and for
a META:group (X-ANDROID-CUSTOM:vnd.android.cursor.item).
--------------------
BORING STUFF
--------------------
Author: Jaidyn Ann <jadedctrl@teknik.io>
License: CC0

10
ex/2.1-inv.vcf Normal file
View File

@ -0,0 +1,10 @@
BEGIN:VCARD
VERSION:2.1
N:Klemenski;Mandy;;Ms.;
FN:Mandy Klemenski
TEL;CELL:+1-404-888-9999
EMAIL;HOME:mommy@emailingtime.co.uk
ADR;HOME:;;1999 Duck Land Road, Candy, IN 88888, USA;;;;
ADR;WORK:;;1889 Sparrow Lane, Candy, IN 88888, USA;;;;
BDAY:1990-04-05
END:VCARD

86
ex/2.1-photo.vcf Normal file
View File

@ -0,0 +1,86 @@
BEGIN:VCARD
VERSION:2.1
N:Klemanto;Mandy;;Ms.;
FN:Mandy Klemanto
TEL;HOME:1-404-123-4535
ADR;HOME:;;1903 Madoka Avenue;Charleton;Utah;98989;USA
PHOTO;ENCODING=BASE64;JPEG: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
o2+rXW7EX9gv3BH8Fdlv7G3uv/9k=
END:VCARD

10
ex/2.1.vcf Normal file
View File

@ -0,0 +1,10 @@
BEGIN:VCARD
VERSION:2.1
N:Klemenski;Mandy;;Ms.;
FN:Mandy Klemenski
TEL;CELL:+1-404-888-9999
EMAIL;HOME:mommy@emailingtime.co.uk
ADR;HOME:;;1999 Duck Road;Candy;IN;888888;USA
ADR;WORK:;;1889 Sparrow Lane;Candy;IN;88888;USA
BDAY:1990-04-05
END:VCARD

20
ex/4.0.vcf Normal file
View File

@ -0,0 +1,20 @@
BEGIN:VCARD
VERSION:4.0
NOTE:A super-valid v4.0 vcard.
N:Alderson;Darlene;;Ms.;
FN:Darlene
Alderson
ORG:Fun Society, Inc.
PHOTO;MEDIATYPE=image/jpeg:http://coinsh.red/u/darlene.jpg
TEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212
TEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212
ADR;TYPE=WORK;PREF=1;LABEL="3027 West 12th Street, Coney Island\nNew York City\, NY\nUnited States of America":;;
3027 West 12th Street, Coney Island;
New York City;NY;
1234;
United States of America
ADR;TYPE=HOME;LABEL="217 E. Broadway\nNew York City\, NY\nUnited States of America":;;217 E. Broadway;New York City;NY;1234;United States of America
EMAIL:d0loreshaze@protonmail.com
REV:20160428T195243Z
x-qq:21588891
END:VCARD

View File

@ -0,0 +1,12 @@
69a70,71
> ## CUSTOM
> set_vcard_group "$vc" "$(catattr -d META:group "$person" 2>/dev/null)"
98a101,108
> }
>
> ## CUSTOM
> function set_vcard_group {
> local vcard="$1"
> local group="$2"
> simple_set_vcard "$vcard" "X-ANDROID-CUSTOM" \
> "vnd.android.cursor.item/relation;${group};1;;;;;;;;;;;;;"

View File

@ -0,0 +1,14 @@
191a192,201
> ## CUSTOM
> function vcard_group {
> local vcard="$1"
> grep "X-ANDROID-CUSTOM:vnd.android.cursor.item/relation;" "$vcard" \
> | sed 's%X-ANDROID-CUSTOM:vnd.android.cursor.item/relation;%%' \
> | sed 's%;.*%%' \
> | clean
> }
>
>
203a214,215
> ## CUSTOM
> addattr -t string META:group "$(vcard_group "$vcard")" "$person"

View File

@ -0,0 +1,11 @@
69a70,71
> ## CUSTOM
> set_vcard_bday "$vc" "$(catattr -d META:birthday "$person" 2>/dev/null)"
98a101,107
> }
>
> ## CUSTOM
> function set_vcard_bday {
> local vcard="$1"
> local bday="$2"
> simple_set_vcard "$vcard" "BDAY" "$bday"

View File

@ -0,0 +1,6 @@
190a191,192
> ## CUSTOM
> function vcard_bday { local vc="$1"; get_vcard_value "$vc" "BDAY" | clean; }
203a206,207
> ## CUSTOM
> addattr -t string META:birthday "$(vcard_bday "$vcard")" "$person"

289
person2vcard Executable file
View File

@ -0,0 +1,289 @@
#!/boot/system/bin/bash
# --------------------------------------
# PERSON2VCARD
# simple script for convertining a person
# into a vcard.
# CC0, jadedctrl@teknik.io
# --------------------------------------
# PERSON INPUT
# --------------------------------------
# convert a given vcard file into a person file
function person_to_vcard {
local person="$1"
if test -n "$OUTPUT_PATH"; then vcard="$OUTPUT_PATH"
else vcard="${person}.vcf"
fi
make_vcard "$vcard"
populate_vcard_person "$vcard" "$person"
finish_vcard "$vcard"
}
# return a properly-formatted colon-delimited address, and a pretty address for
# labels. the former on first line, latter on the second.
function person_address {
local person="$person"
local address="$(catattr -d META:address "$person" 2>/dev/null)"
local city="$(catattr -d META:city "$person" 2>/dev/null)"
local state="$(catattr -d META:state "$person" 2>/dev/null)"
local zip="$(catattr -d META:zip "$person" 2>/dev/null)"
local country="$(catattr -d META:country "$person" 2>/dev/null)"
if test -z "$city" -a -z "$address"; then return 2; fi
echo ";;${address};${city};${state};${zip};${country}"
echo "${address}, ${city}, ${state} ${zip}, ${country}"
}
# VCARD OUTPUT
# --------------------------------------
# create a fresh, blank vcard
function make_vcard {
local vcard="$1"
printf 'BEGIN:VCARD\nVERSION:%s\n' "$VERSION" >> "$vcard"
addattr -t mime BEOS:TYPE text/x-vcard "$vcard"
}
# wrap up the current vcard
function finish_vcard {
local vcard="$1"
date +"REV:%Y%m%d" >> "$vcard"
echo "END:VCARD" >> "$vcard"
}
# populate a vcard with a person's attributes
function populate_vcard_person {
local vc="$1"
local person="$2"
set_vcard_name "$vc" "$(catattr -d META:name "$person" 2>/dev/null)"
set_vcard_org "$vc" "$(catattr -d META:company "$person" 2>/dev/null)"
set_vcard_url "$vc" "$(catattr -d META:url "$person" 2>/dev/null)"
set_vcard_email "$vc" "$(catattr -d META:email "$person" 2>/dev/null)"
set_vcard_phone "$vc" "$(catattr -d META:wphone "$person" 2>/dev/null)" "WORK"
set_vcard_phone "$vc" "$(catattr -d META:hphone "$person" 2>/dev/null)" "HOME"
set_vcard_nick "$vc" "$(catattr -d META:nickname "$person" 2>/dev/null)"
set_vcard_address "$vc" "$(person_address "$person")" "HOME"
set_vcard_photo "$vc" "$person"
}
# set the name and full-name of a vcard.
function set_vcard_name {
local vcard="$1"
local name="$2"
simple_set_vcard "$vcard" "N" "$(infer_name "$name")"
simple_set_vcard "$vcard" "FN" "$name"
}
# set the email of a vcard.
function set_vcard_email {
local vcard="$1"
local email="$2"
simple_set_vcard "$vcard" "EMAIL" "$email"
}
# set a vcard's url attribute.
function set_vcard_url {
local vcard="$1"
local url="$2"
simple_set_vcard "$vcard" "URL" "$url"
}
# set a vcard's organization attribute.
function set_vcard_org {
local vcard="$1"
local org="$2"
simple_set_vcard "$vcard" "ORG" "$org"
}
# set a vcard's nick. only for 3.0 and 4.0; 2.1 doesn't support NICKNAME.
function set_vcard_nick {
local vcard="$1"
local nick="$2"
if test -z "$nick"; then return 2; fi
if test "$VERSION" = "3.0" -o "$VERSION" = "4.0"; then
echo "NICKNAME:${nick}" >> "$vcard"
fi
}
# set a vcard's phone-number of the given type.
function set_vcard_phone {
local vcard="$1"
local number="$2"
local type="$3"
if test -z "$number"; then return 2; fi
if test -z "$type"; then type="HOME"; fi
local prefix=""
if test "$VERSION" = "2.1"; then prefix="TEL;${type};VOICE:"
elif test "$VERSION" = "3.0"; then prefix="TEL;TYPE=${type},VOICE:"
elif test "$VERSION" = "4.0"
then prefix="TEL;TYPE=${type},voice;VALUE=uri:tel:"
fi
echo "${prefix}${number}" >> "$vcard"
}
# sets a vcard's address and address label
# ($addresses is a two-lined string, with the colon-delimited address on top
# and the user-friendly comma-delimited on bottom.)
function set_vcard_address {
local vcard="$1"
local addresses="$2"
local type="$3"
local address="$(echo "$addresses" | head -1)"
local label="$(echo "$addresses" | tail -1)"
local addrstr=""
local labelstr=""
if test "$VERSION" = "2.1" -o "$VERSION" = "3.0"; then
labelstr="LABEL;TYPE=${type}:${label}"
fi
if test "$VERSION" = "2.1"; then
addrstr="ADR;${type}:${address}"
elif test "$VERSION" = "3.0"; then
addrstr="ADR;TYPE=${type}:${address}"
elif test "$VERSION" = "4.0"; then
addrstr="ADR;TYPE=${type};LABEL=\"${label}\":${address}"
fi
if test -z "$address" -o -z "$label"; then return 2; fi
echo "$addrstr" >> "$vcard"
if test -n "$labelstr"; then
echo "$labelstr" >> "$vcard"
fi
}
# embeds the given photo into the vcard file (base64), if possible
function set_vcard_photo {
local vcard="$1"
local photo="$2"
if is_image "$photo"; then ls>/dev/null; else return 2; fi
local mime="$(file --mime-type "$photo")"
local type="$(file "$photo" | awk -F : '{print $1}' | awk '{print $1}')"
echo >> "$vcard"
if test "$VERSION" = "2.1"; then
echo "PHOTO;${type};ENCODING=BASE64:$(base64 -w 79 "$photo")" \
>>"$vcard"
elif test "$VERSION" = "3.0"; then
echo "PHOTO;TYPE=${type};ENCODING=b:$(base64 -w 79 "$photo")" \
>> "$vcard"
elif test "$VERSION" = "4.0"; then
echo "PHOTO:data:${mime};base64,$(base64 -w 79 "$photo")" \
>> "$vcard"
fi
echo >> "$vcard"
}
# generic function for setting simple attrs of vcards.
function simple_set_vcard {
local vcard="$1"; local key="$2"; local val="$3"
if test -z "$val"; then return 2; fi
echo "${key}:${val}" >> "$vcard"
}
# MISC
# --------------------------------------
# takes a trad. comma-delimited address and converts to vcard format
function infer_name {
local names="$1"
local inferred_name=""
local honorific=""
local slot_count=5
local name_count="$(echo "$names" | wc -w)"
for name in $names; do
if is_honorific "$name"; then
honorific="$name"
else
inferred_name="${name};${inferred_name}"
fi
done
local i=""
if test "$name_count" -ne "$slot_count"; then
i="$((slot_count - $name_count - 1))"
while test "$i" -ne 0; do
inferred_name="${inferred_name};"
i="$((i - 1))"
done
if test -n "$honorific" -a "$name_count" -lt "$slot_count"; then
inferred_name="${inferred_name}${honorific};"
fi
fi
echo "$inferred_name"
}
# ok so this is super language-dependant and is omega cringe
# you'll probably have to modify this for your locale
function is_honorific {
local name="$1"
local honorifics="ms. mrs. mr. s-ro s-ano s-ino"
if echo "$honorifics" | grep -i "$name" > /dev/null; then
return 0
else
return 1
fi
}
# check whether or not a file is an image
function is_image {
local file="$1"
file "$file" | grep "image" > /dev/null
}
# INVOCATION
# --------------------------------------
function help {
echo "usage: $(basename $0) [-h] [-v version] [-o path] person"
echo
echo \
"Creates a vcard from the given person file's attributes. By default will
output to a file of the same name with a .vcf file-extension.
-o changes output vcard filename
-v specify vcard version: 2.1, 3.0, or 4.0. Defaults to 4.0.
a random URL, and use it as the vcard's photo URL.
-h print this message."
exit 1
}
VERSION="4.0"
OUTPUT_PATH=""
while getopts ":hUo:v:" arg; do
case $arg in
o) OUTPUT_PATH="$OPTARG";;
v) if test "$OPTARG" = "4.0" -o "$OPTARG" = "3.0" \
-o "$OPTARG" = "2.1"; then
VERSION="$OPTARG"
else
echo "Invalid version number." >&2
exit 3
fi;;
h) help;;
esac
done
shift $(($OPTIND - 1))
remaining_args="$@"
if test -z "$1"; then help
else PERSON="$1"
fi
person_to_vcard "$PERSON"

429
vcard2person Executable file
View File

@ -0,0 +1,429 @@
#!/boot/system/bin/bash
# --------------------------------------
# VCARD2PERSON
# simple script for convertining a vcard
# into a haiku person file.
# CC0, jadedctrl@teknik.io
# --------------------------------------
# VCARD INPUT
# --------------------------------------
# convert a given vcard file into a person file
function vcard_to_person {
local vcard="$1"
local person="$2"
if test -n "$OUTPUT_PATH"; then person="$OUTPUT_PATH"; fi
if test -n "$OUTPUT_DIR"; then person="${OUTPUT_DIR}/${person}"; fi
make_person "$person"
populate_person_vcard "$person" "$vcard"
}
# sometimes vcf files contain multiple vcard definitions.
# if so, then process each vcard individually.
function vcf_to_persons {
orig_vcf="$1"
vcf="$(mktemp -t 'v2p-XXXX')"
vcard="$(mktemp -t 'v2p-XXXX')"
cp "$orig_vcf" "$vcf"
while grep "END:VCARD" "$vcf" > /dev/null; do
sed '/END:VCARD/q' "$vcf" \
> "$vcard"
vcard_to_person "$vcard" "$(vcard_name "$vcard"| remove_cr)"
tail -n +2 "$vcf" \
| sed '/BEGIN:VCARD/,$!d' \
> "$vcf.tmp"
mv "$vcf.tmp" "$vcf"
done
rm "$vcf" "$vcard"
}
# get a given value from a vcard
function get_vcard_value {
local file="$1"
local key="$2"
# local matching="$(grep "^${key}" "$file")"
local matching="$(grep -n "^${key}" "$file" | awk -F : '{print $1}')"
echo "===${matching}">>/tmp/dad
if test "$(line_length "$matching")" -gt 1; then
matching="$(select_match "$file" "$key" "$matching")"
fi
local end_line="$(next_match "$file" "$matching" '^.*[A-Z a-z "]:')"
cat "$file" \
| get_line "$matching" \
| cut --complement -d ':' -f 1
cat "$file" \
| line_range "$((matching + 1))" "$((end_line - 1))"
}
# print how many matches a given key has
function vcard_match_count {
local file="$1"
local key="$2"
line_length "$(grep "^${key}" "$file")"
}
# select a given match; for when there are mulitple possible slots
function select_match {
local file="$1"
local key="$2"
local matches="$3"
local match_count="$(line_length "$matches")"
echo "There are a few matching slots for ${key}." 1>&2
echo "Which one do you want to use?" 1>&2
echo "(0 for skip)" 1>&2
local prompt="[0-${match_count}] >> "
select_line "$file" "$prompt" "$matches"
}
# return a given field-number of vcard's adr
function vcard_adr_value {
local adr="$1"
local index="$2"
echo "$adr" \
| awk -v ind="$index" -F ';' '{print $ind}'
}
# get a vcard's telephone number
function vcard_tel {
local file="$1"
get_vcard_value "$file" "TEL" \
| sed 's%tel:%%' \
| clean
}
# puts a vcard's photo to a given output, if it exists
function vcard_photo {
local vcard="$1"
local output="$2"
local photo_value="$(get_vcard_value "$vcard" "PHOTO")"
if test -z "$photo_value"; then return 2; fi
if echo "$photo_value" | grep "http://">/dev/null; then
vcard_photo_url "$photo_value" "$output"
else
vcard_photo_base64 "$photo_value" "$output";
fi
}
# if vcard has a url to a photo (and it's an image), save to output file
function vcard_photo_url {
local photo="$1"
local output="$2"
local uri="$(echo "$photo" | sed 's%.*http%http%')"
if echo "$uri" | grep http > /dev/null; then
wget --quiet -O "${output}.tmp" "$uri"
if file "${output}.tmp" | grep "image" > /dev/null; then
cat "${output}.tmp" > "$output"
fi
rm "$output.tmp"
fi
}
# tries to decode base64 photo from a vcard, save to an output file
# if the GNU base64 decoder fails, tries the openssl decoder.
# ... if *that* fails, tries the python one.
# (can you tell I really want contact photos? lmao)
function vcard_photo_base64 {
local photo="$1"
local output="$2"
echo "$photo" \
| clean \
| base64 -d \
2> /dev/null \
> "${output}.tmp"
local result="$?"
if test "$result" -ne 0; then
echo "$photo" \
| openssl enc -base64 -d \
2> /dev/null \
> "${output}.tmp"
result="$?"
fi
if test "$result" -ne 0; then
echo "$photo" \
| clean \
| python -m base64 -d \
2> /dev/null \
> "${output}.tmp"
result="$?"
fi
if test "$result" -eq 0; then
cat "${output}.tmp" > "${output}"
fi
rm "${output}.tmp"
}
# pretty self-explanatory
function vcard_version { local v="$1"; get_vcard_value "$v" "VERSION" | clean; }
function vcard_name { local vc="$1"; get_vcard_value "$vc" "FN" | clean; }
function vcard_org { local vc="$1"; get_vcard_value "$vc" "ORG" | clean; }
function vcard_url { local vc="$1"; get_vcard_value "$vc" "URL" | clean; }
function vcard_email { local vc="$1"; get_vcard_value "$vc" "EMAIL" | clean; }
function vcard_adr { local vc="$1"; get_vcard_value "$vc" "ADR" | clean; }
function adr_address { local adr="$1";vcard_adr_value "$adr" 3 | clean; }
function adr_city { local adr="$1";vcard_adr_value "$adr" 4 | clean; }
function adr_state { local adr="$1";vcard_adr_value "$adr" 5 | clean; }
function adr_zip { local adr="$1";vcard_adr_value "$adr" 6 | clean; }
function adr_country { local adr="$1";vcard_adr_value "$adr" 7 | clean; }
# PERSON OUTPUT
# --------------------------------------
# populate the given person with the data of the given vcard
function populate_person_vcard {
local person="$1"
local vcard="$2"
addattr -t string META:name "$(vcard_name "$vcard")" "$person"
addattr -t string META:company "$(vcard_org "$vcard")" "$person"
addattr -t string META:email "$(vcard_email "$vcard")" "$person"
populate_person_telephone "$person" "$vcard"
populate_person_address "$person" "$vcard"
vcard_photo "$vcard" "$person"
}
# populate a person with a vcard's address data
function populate_person_address {
local person="$1"
local vcard="$2"
local adr="$(vcard_adr "$vcard")"
# some implementations (wrongly) put all address info in the address
# slot, delimiting with commas and leaving other slots blank.
# we can try and work around that.
if test -n "$(adr_address "$adr")" -a -z "$(adr_city "$adr")"; then
if echo "$adr" | grep "," > /dev/null; then
adr="$(infer_comma_address "$adr")"
fi
fi
addattr -t string META:address "$(adr_address "$adr")" "$person"
addattr -t string META:city "$(adr_city "$adr")" "$person"
addattr -t string META:state "$(adr_state "$adr")" "$person"
addattr -t string META:zip "$(adr_zip "$adr")" "$person"
addattr -t string META:country "$(adr_country "$adr")" "$person"
}
# populate a person with a vcard's phone data
function populate_person_telephone {
local person="$1"
local vcard="$2"
if test "$(vcard_match_count "$vcard" "TEL")" -eq 1; then
addattr -t string META:hphone "$(vcard_tel "$vcard")" "$person"
elif test "$(vcard_match_count "$vcard" "TEL")" -gt 1; then
echo "Home number:"
addattr -t string META:hphone "$(vcard_tel "$vcard")" "$person"
echo "Work number:"
addattr -t string META:wphone "$(vcard_tel "$vcard")" "$person"
fi
}
# make a new person file
function make_person {
local file="$1"
touch "$file"
addattr -t mime BEOS:TYPE application/x-person "$file"
}
# MISC
# --------------------------------------
# takes a trad. comma-delimited address and converts to vcard format
function infer_comma_address {
local addr="$1"
local addr_split="$(echo "$addr"| tr -d ';'| tr ',' '\n'| sed 's%^ %%')"
local city_zip_line="$(echo "$addr_split" | grep '[0-9]$')"
local zip="$(echo "$city_zip_line" | awk -F ' ' '{print $NF}')"
echo ";;${addr_split}" \
| sed 's% '"$zip"'%\n'"$zip"'%' \
| tr '\n' ';' \
| sed 's%\;$%%'
}
# have the user select a given line (enumerates output, captive)
# also outputs everything to stderr
function select_line {
local file="$1"
local prompt="$2"
local line_nums="$3"
local index=1
for line in $line_nums; do
printf '%s ' "$index" >&2
cat "$file" \
| get_line "$line" \
>&2
index="$((index + 1))"
done
index="$(select_number 0 $(line_length "$line_nums") "$prompt")"
echo "$line_nums" \
| get_line "$index"
}
# have the user choose a number within the given range
# ouputs everything to stderr except the number
function select_number {
local min=$1
local max=$2
local prompt="$3"
printf "$prompt" >&2
read response
if test "$response" -le "$max" -a "$response" -ge "$min"; then
echo $response
else
select_number $min $max "$prompt"
fi
}
# echoes everything piped into it.
# useful for taking piped info in a shell function.
function reade {
local stack=""
while read input; do
stack="$(printf '%s\n%s' "$stack" "$input")"
done
echo "$stack"
}
# self-explanatory, no?
function escape_spaces { sed 's% %\\ %g'; }
function remove_cr { tr -d '\r'; }
function replace_nl { tr '\n' ' '; }
function trim { awk '{$1=$1};1'; }
# does some handy sanitization-work
function clean {
remove_cr \
| replace_nl \
| trim
}
# check whether or not a file is an image
function is_image {
local file="$1"
file "$file" | grep "image" > /dev/null
}
# prints the amount of lines a string has
function line_length {
local lines="$1"
echo "$lines" \
| wc -l
}
# prints the line at given index
function get_line {
local line_index="$1"
awk -v ind="$line_index" 'NR==ind'
}
# prints lines after given index (inclusive)
function after_line {
local line_index="$1"
awk -v ind="$line_index" 'NR>=ind'
}
# prints lines before given index (inclusive)
function before_line {
local line_index="$1"
awk -v ind="$line_index" 'NR<=ind'
}
# prints a range of lines (inclusive)
function line_range {
local min="$1"
local max="$2"
awk -v min="$min" -v max="$max" 'min <= NR && NR <= max'
}
# prints the line index of the next match following a given line index
function next_match {
local file="$1"
local after_index="$2"
local regex="$3"
cat "$file" \
| after_line "$((after_index+1))" \
| grep -n "${regex}" \
| head -1 \
| awk -v last="$after_index" -F : '{print last + $1}' \
| bc
}
# INVOCATION
# --------------------------------------
function help {
echo "usage: $(basename $0) vcard [-h] [-d directory] [-o path] file"
echo
echo \
"People files will be created using the data from a vcard. Each vcard defined
by a given vcf/vcard file have its person file be named after the vcard's
full-name.
-o changes the output person filename
-d outputs all people files to a given directory-- useful if a file
defines multiple vcards.
-h prints this message."
exit 1
}
OUTPUT_DIR=""
OUTPUT_PATH=""
while getopts ":hd:o:" arg; do
case $arg in
o) OUTPUT_PATH="$OPTARG";;
d) if test -d "$OPTARG"; then
OUTPUT_DIR="$OPTARG"
else
echo "Directory does not exist." >&2
exit 3
fi;;
h) help;;
esac
done
shift $(($OPTIND - 1))
remaining_args="$@"
if test -z "$1"; then help
else VCARD="$1"
fi
vcf_to_persons "$VCARD"