Initial commit

This commit is contained in:
Ben Ashton 2022-09-28 23:05:02 -06:00
commit a18f52b6a1
3 changed files with 197 additions and 0 deletions

50
README.md Normal file
View File

@ -0,0 +1,50 @@
# Exprash
An Express-like HTTP router for Bash compatible with CGI 1.1
I'm aware that the name of this project evokes a feeling of physical disgust. That is by design.
## Usage/Examples
Here is some leaked source code for my brand new Cat website. Do not steal!
```shell
#!/bin/bash
printf '%s\n\n' 'Content-Type: text/html';
source ../src/exprash.sh;
declare -A cats;
cats[calico]="Calico";
cats[sphynx]="Sphynx";
cats[ragdoll]="Ragdoll";
cats[scottish_fold]="Scottish Fold";
get '/' && {
printf '<h1>Cats</h1>\n';
printf '<ul>\n';
for key in "${!cats[@]}"; do
printf '<li><a href="cats/%s">%s</a></li>\n' "$key" "${cats[$key]}";
done
printf '</ul>\n';
}
get '/cats/:cat' && {
key=$(param 'cat');
if [[ -v "cats[$key]" ]]; then
printf '<h1>Your Cat: %s</h1>\n' "${cats[$key]}";
else
next;
fi;
}
all '/cats/:cat' && {
printf '<h1>Error: Cannot find that cat</h1>\n';
}
```
## License
[WTFPL](http://www.wtfpl.net/txt/copying/)

33
examples/cats.sh Normal file
View File

@ -0,0 +1,33 @@
#!/bin/bash
printf '%s\n\n' 'Content-Type: text/html';
source ../src/exprash.sh;
declare -A cats;
cats[calico]="Calico";
cats[sphynx]="Sphynx";
cats[ragdoll]="Ragdoll";
cats[scottish_fold]="Scottish Fold";
get '/' && {
printf '<h1>Cats</h1>\n';
printf '<ul>\n';
for key in "${!cats[@]}"; do
printf '<li><a href="cats/%s">%s</a></li>\n' "$key" "${cats[$key]}";
done
printf '</ul>\n';
}
get '/cats/:cat' && {
key=$(param 'cat');
if [[ -v "cats[$key]" ]]; then
printf '<h1>Your Cat: %s</h1>\n' "${cats[$key]}";
else
next;
fi;
}
all '/cats/:cat' && {
printf '<h1>Error: Cannot find that cat</h1>\n';
}

114
src/exprash.sh Normal file
View File

@ -0,0 +1,114 @@
#!/bin/bash
# shellcheck disable=SC2034 # false flags nameref params
# =============
# Route Methods
# =============
# $1: path
function get() {
[ "$REQUEST_METHOD" != "GET" ] && return 1;
all "$1";
}
# $1: path
function post() {
[ "$REQUEST_METHOD" != "POST" ] && return 1;
all "$1";
}
# $1: path
function put() {
[ "$REQUEST_METHOD" != "PUT" ] && return 1;
all "$1";
}
# $1: path
function delete() {
[ "$REQUEST_METHOD" != "DELETE" ] && return 1;
all "$1";
}
# $1: path
function all() {
[ "$routeHandled" -eq 1 ] && return 1;
# Reset params
params=();
pathMatch "$1" params || return 1;
# # if path ooesn't match PATH_INFO: return 1
routeHandled=1;
return 0;
}
function next() {
routeHandled=0;
}
# =================
# Request Functions
# =================
# $1: param name
function param() {
printf '%s\n' "${params[$1]}";
}
# $1: param name
function hasParam() {
[[ -v "params[$1]" ]];
}
# =========
# Internals
# =========
# $1: path
# $2: (nameref) array
function pathToArray() {
readarray -t "$2" < <(printf '%s\n' "$1" | tr '/' '\n' | grep .);
}
# $1: route
# $2: (nameref) associative array for params
function pathMatch() {
[ -z ${PATH_INFO+x} ] && return 1;
[ -z ${1+x} ] && return 1;
local path="$PATH_INFO";
local route="$1";
local pathArr;
pathToArray "$path" pathArr;
local routeArr;
pathToArray "$route" routeArr;
# Params associative array
local -n routeParams="$2";
# Not a match if pathArr and routeArr have different length
[ "${#pathArr[@]}" -ne "${#routeArr[@]}" ] && return 1;
for ((i=0; i<${#routeArr[@]}; i++)); do
local routeComponent="${routeArr[$i]}";
local pathComponent="${pathArr[$i]}";
# If route component starts with ":"
if [[ "$routeComponent" == :* ]]; then
routeParams["${routeComponent:1}"]="$pathComponent";
else
# Confirm paths match
[ "$routeComponent" != "$pathComponent" ] && return 1;
fi;
done;
return 0;
}
# =======
# Globals
# =======
routeHandled=0;
declare -A params;