You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
5.7 KiB
253 lines
5.7 KiB
#!/usr/bin/env n0m |
|
$ #!/bin/bash |
|
Content-Type: text/html |
|
|
|
<{ |
|
# Constants |
|
# ========= |
|
true=1; |
|
false=0; |
|
|
|
restaurant_file="restaurants.txt"; |
|
title="FeedMe"; |
|
|
|
# Components |
|
# ========== |
|
|
|
# $1: string: input value |
|
# $2: number: 0=existing restaurant, 1=new restaurant |
|
restaurant_form () { |
|
enforce_arguments "${FUNCNAME[0]}" "$#" 2; |
|
local icon_class; |
|
if [ "$2" -eq 0 ]; then |
|
icon_class="ri-save-2-fill"; |
|
else |
|
icon_class="ri-add-line"; |
|
fi |
|
}> |
|
<form class="restaurant-form" method="POST" action=""> |
|
<input type="hidden" name="old" value="<% $1 %>" /> |
|
<input |
|
type="text" |
|
placeholder="Restaurant" |
|
name="new" |
|
value="<% $1 %>" |
|
/> |
|
<label class="button <% $icon_class %>"> |
|
$ local action; |
|
$ if [ "$2" -eq 0 ]; then |
|
$ action="save"; |
|
$ else |
|
$ action="add"; |
|
$ fi; |
|
<input type="submit" name="action" value="<% $action %>" /> |
|
</label> |
|
$ if [ "$2" -eq 0 ]; then |
|
<label class="button ri-delete-bin-2-fill"> |
|
<input type="submit" name="action" value="delete" /> |
|
</label> |
|
$ fi |
|
</form> |
|
<{ |
|
} |
|
|
|
# Utility Functions |
|
# ================= |
|
|
|
# $1: string: function name |
|
# $2: number: $# |
|
# $3: number: expected number of arguments |
|
enforce_arguments () { |
|
if [ "$2" -lt "$3" ]; then |
|
printf '%s %s %s\n' 'Function:' "$1" 'called with too few arguments'; |
|
exit 1; |
|
elif [ "$2" -gt "$3" ]; then |
|
printf '%s %s %s\n' 'Function:' "$1" 'called with too many arguments'; |
|
exit 1; |
|
fi; |
|
} |
|
|
|
decode_uri () { |
|
local i="${*//+/ }"; |
|
echo -e "${i//%/\\x}"; |
|
} |
|
|
|
|
|
# Restaurant List Functions |
|
# ========================= |
|
|
|
# $1: string: restaurant to remove |
|
remove_restaurant () { |
|
enforce_arguments "${FUNCNAME[0]}" "$#" 1; |
|
local temp_file; |
|
temp_file=$(mktemp); |
|
grep -Fxv "$1" "$restaurant_file" > "$temp_file"; |
|
mv "$temp_file" "$restaurant_file"; |
|
} |
|
|
|
# $1: string: restuarant to add |
|
add_restaurant () { |
|
enforce_arguments "${FUNCNAME[0]}" "$#" 1; |
|
local temp_file; |
|
temp_file=$(mktemp); |
|
(grep -Fxv "$1" "$restaurant_file"; printf '%s\n' "$1") > "$temp_file"; |
|
mv "$temp_file" "$restaurant_file"; |
|
} |
|
|
|
# $1 (optional): number: 0=don't overwrite, 1=overwrite |
|
todays_restaurant () { |
|
local today; |
|
today="$(date +'%Y%m%d')"; |
|
local filename="$today.pick"; |
|
if [ -n "$1" ] && [ "$1" -ne 0 ]; then |
|
rm "$filename"; |
|
fi; |
|
if [ ! -f "$filename" ]; then |
|
shuf -n 1 "$restaurant_file" > "$filename"; |
|
fi; |
|
cat "$filename"; |
|
} |
|
|
|
# Initialization |
|
# ============== |
|
|
|
# Make sure restaurant file eixsts |
|
touch -a "$restaurant_file"; |
|
|
|
# Parse body |
|
# ---------- |
|
declare -A body; |
|
while IFS= read -d '&' -r pair || [ "$pair" ]; do |
|
name=$(decode_uri "${pair%%=*}"); |
|
value=$(decode_uri "${pair#*=}"); |
|
if [ -n "$name" ]; then |
|
body["$name"]="$value"; |
|
fi; |
|
done |
|
|
|
# Perform requested action |
|
# ------------------------ |
|
case "${body['action']}" in |
|
add) |
|
add_restaurant "${body['new']}"; |
|
;; |
|
delete) |
|
remove_restaurant "${body['old']}"; |
|
;; |
|
save) |
|
remove_restaurant "${body['old']}"; |
|
add_restaurant "${body['new']}"; |
|
;; |
|
new_restaurant) |
|
todays_restaurant $true >/dev/null; |
|
;; |
|
esac |
|
|
|
}><!doctype html> |
|
<html> |
|
<head> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<meta property="og:title" content="FeedMe"> |
|
<meta property="og:type" content="website"> |
|
<meta property="og:url" content="https://n0m.org/feedme"> |
|
<meta property="og:image" content="https://n0m.org/feedme/images/food.png"> |
|
<meta property="og:description" content="Today's Restaurant: <$% todays_restaurant %>"> |
|
<title><% $title %></title> |
|
<style> |
|
:root { |
|
--border-color: gray; |
|
} |
|
|
|
html, body { |
|
margin: 0; |
|
padding: 0; |
|
} |
|
|
|
html { |
|
font-family: sans-serif; |
|
} |
|
|
|
body { |
|
background-color: #f5f5f5; |
|
} |
|
|
|
\#title { |
|
color: rgba(0, 0, 0, 0.15); |
|
text-align: center; |
|
} |
|
|
|
\#content { |
|
margin: 0 auto; |
|
width: calc(min(400px, 100vw - 1.5em)); |
|
} |
|
|
|
\#todays-restaurant { |
|
text-align: center; |
|
font-size: 1.3em; |
|
} |
|
|
|
\#restaurant-list { |
|
list-style-type: none; |
|
padding: 0; |
|
} |
|
|
|
.restaurant-form { |
|
display: flex; |
|
} |
|
.restaurant-form input[type='text'] { |
|
flex-grow: 1; |
|
border: 1px solid var(--border-color); |
|
padding: 0.4em 0.7em; |
|
} |
|
.restaurant-form label.button { |
|
border-radius: 0; |
|
} |
|
.restaurant-form > :not(:first-child) { |
|
border-left: 0; |
|
} |
|
\#restaurant-list > li:not(:last-child) .restaurant-form > * { |
|
border-bottom: 0; |
|
} |
|
|
|
label.button { |
|
display: flex; |
|
border: 1px solid var(--border-color); |
|
border-radius: 0.3em; |
|
padding: 0.2em 0.5em; |
|
background: #FDFDFD; |
|
align-items: center; |
|
} |
|
label.button > span:first-of-type { |
|
flex-grow: 1; |
|
text-align: center; |
|
} |
|
label.button > input { |
|
display: none; |
|
} |
|
</style> |
|
<link |
|
href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" |
|
rel="stylesheet" |
|
> |
|
</head> |
|
<body> |
|
<div id="content"> |
|
<h1 id="title"><% $title %></h1> |
|
<p id="todays-restaurant"> |
|
Today's Restaurant: <strong><$% todays_restaurant %></strong> |
|
<form method="POST" action=""> |
|
<label class="button"> |
|
<span>I want to go somewhere else</span> |
|
<input type="submit" name="action" value="new_restaurant" /> |
|
</label> |
|
</form> |
|
</p> |
|
<ul id="restaurant-list"> |
|
$ while IFS= read -r line || [ "$line" ]; do |
|
<li><$!% restaurant_form "$line" $false %></li> |
|
$ done <restaurants.txt #> |
|
<li><$!% restaurant_form "" $true %></li> |
|
</ul> |
|
</div> |
|
</body> |
|
</html>
|
|
|