]> git.plutz.net Git - invoices/commitdiff
initial commit
authorPaul Hänsch <paul@plutz.net>
Wed, 14 Nov 2018 00:26:12 +0000 (01:26 +0100)
committerPaul Hänsch <paul@plutz.net>
Wed, 14 Nov 2018 00:26:12 +0000 (01:26 +0100)
.gitmodules [new file with mode: 0644]
cgilite [new submodule]
invoices.cgi [new file with mode: 0755]
invoices.css [new file with mode: 0755]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..5c32300
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "cgilite"]
+       path = cgilite
+       url = http://git.plutz.net/git/cgilite
diff --git a/cgilite b/cgilite
new file mode 160000 (submodule)
index 0000000..5c24129
--- /dev/null
+++ b/cgilite
@@ -0,0 +1 @@
+Subproject commit 5c24129a2347b2be54fedfec74acc156758e3533
diff --git a/invoices.cgi b/invoices.cgi
new file mode 100755 (executable)
index 0000000..d079ced
--- /dev/null
@@ -0,0 +1,198 @@
+#!/bin/sh
+
+_EXEC="$(realpath "${0%/*}")"
+. "$_EXEC/cgilite/cgilite.sh"
+. "$_EXEC/cgilite/storage.sh"
+
+debug(){
+  if [ $# = 0 ]; then
+    tee /dev/stderr
+  else
+    printf %s\\n "$*" >/dev/stderr
+  fi
+}
+
+timeid(){
+  # generate time based ID
+  # Fixme: Unix time stamps assumed to be 32bit always
+  d=$(date +%s)
+  { printf $(
+      while [ "$d" -gt 0 ]; do
+        printf \\%o\\n $((d % 256))
+        d=$((d / 256))
+      done |tac |tr -d \\n
+    )
+    head -c5 /dev/urandom
+  } \
+  | uuencode -m - \
+  | sed -n '2{y;+/;:_;;p}'
+}
+
+checkid(){
+  grep -m 1 -xE '[0-9a-zA-Z:_]{12}';
+}
+
+yield_page(){
+printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
+./cgilite/html-sh.sed <<EOF
+[html [head
+  [meta name="viewport" content="width=device-width"]
+  [link rel="stylesheet" type="text/css" href="invoices.css"]
+  [title Invoices]
+] [body class="$1"
+  [div #menu
+    [a "/invoices/" Invoices]
+    [a "/clients/" Clients]
+    [a "/senders/" Senders]
+  ]
+  $(cat)
+] ]
+EOF
+}
+
+edit_client(){
+  id="$1"
+  if [ -f "clients/$id" ]; then
+    read -r address hourly <"clients/$id"
+  fi
+  [ "$address" ] || address="address="
+  [  "$hourly" ] ||  hourly="hourly="
+  printf '
+    [form method="POST" action="/update_client"
+      [hidden "id" "%s"]
+      <textarea name="address" placeholder="address">
+%s</textarea>
+      [label for=hourly Hourly Rate:]
+      [input #hourly type=number name=hourly value="%s"]
+      [submit "update" "update" Update]
+    ]' \
+    "$(HTML $id)" \
+    "$(UNSTRING "${address#address=}" |HTML)" \
+    "$(UNSTRING "${hourly#hourly=}" |grep -xE '[0-9]+')"
+}
+
+edit_sender(){
+  id="$1"
+  if [ -f "senders/$id" ]; then
+    address="$(cat "senders/$id")"
+  fi
+  [ "$address" ] || address="Name
+Street
+City
+
+Phone:
+000 000000
+
+Tax no.
+xxx / 000 / ###
+"
+  printf '
+    [form method="POST" action="/update_sender"
+      [hidden "id" "%s"]
+      <textarea name="address" placeholder="address">%s</textarea>
+      [submit "update" "update" Update]
+    ]' \
+    "$(HTML $id)" \
+    "$(HTML "${address}")"
+}
+
+list_clients(){
+  [ -d clients/ ] && for c in clients/*; do
+    read -r address hourly <"$c"
+    address="$(UNSTRING "${address#address=}")"
+    [ "$address" ] || address="(no address)"
+    printf '[div .client .address <!--
+      -->%s[a href="/clients/%s" Edit]]
+    ' "$(HTML "$address")" "$(HTML "${c#clients/}")"
+  done
+}
+
+list_senders(){
+  [ -d senders/ ] && for s in senders/*; do
+    address=$(cat "$s")
+    [ "$address" ] || address="(no address)"
+    printf '[div .sender .address <!--
+      -->%s[a href="/senders/%s" Edit]]
+    ' "$(HTML "$address")" "$(HTML "${s#senders/}")"
+  done
+}
+
+list_invoices(){
+  [ -d invoices/ ] && for i in invoices/*; do
+    read -r sender client date number vat<<-EOF
+       sed q "$i"
+       EOF
+    [ -f "senders/${sender#sender=}" ] \
+    && sender="$(sed q "senders/${sender#sender=}")" \
+    || sender="(unset)"
+    [ -f "clients/${client#client=}" ] \
+    && client="$(sed q "client/${client#client=}")" \
+    || client="(unset)"
+    [ "$date" -gt 0 ] \
+    && date="$(date -d @$date +%x)" \
+    || date="(unset)"
+
+    printf '[div .invoice
+      [h2
+          %s]
+      [label From:] %s [label To:] %s [label on] %s
+      [a href="/invoices/%s" Edit]
+    ]' "$(HTML "$number")" "$(HTML "$sender")" \
+       "$(HTML "$client")" "$(HTML "$date")"
+  done
+}
+
+info="$(PATH "${PATH_INFO}")"
+
+case $info in
+  /invoices.css)
+    . "$_EXEC/cgilite/file.sh"
+    FILE "$_EXEC/invoices.css"
+    ;;
+  /clients)
+    { list_clients
+      printf '[a .new href="/clients/%s" New]' "$(timeid)"
+    } | yield_page clients
+    ;;
+  /clients/*)
+    edit_client "${info#/clients/}" |yield_page client
+    ;;
+  /update_client)
+    id="$(POST id |checkid)"
+    if [ "$(POST update)" = update -a "$id" ]; then
+      mkdir -p clients
+      printf 'address=%s       hourly=%s' \
+        "$(POST address |STRING)" "$(POST hourly |STRING)" \
+        >"clients/$id"
+    else
+      echo Invalid Data "$(POST id)" "$(POST update)" >&2
+    fi
+    REDIRECT /clients/
+    ;;
+  /senders)
+    { list_senders
+      printf '[a .new href="/senders/%s" New]' "$(timeid)"
+    } | yield_page senders
+    ;;
+  /senders/*)
+    edit_sender "${info#/senders/}" |yield_page sender
+    ;;
+  /update_sender)
+    id="$(POST id |checkid)"
+    if [ "$(POST update)" = update -a "$id" ]; then
+      mkdir -p senders
+      POST address >"senders/$id"
+    fi
+    REDIRECT /senders/
+    ;;
+  /invoices)
+    { list_invoices
+      printf '[a href="/invoices/%s" New]' "$(timeid)"
+    } | yield_page invoices
+    ;;
+  /invoice/*)
+    edit_invoice "${info#/invoices/}" |yield_page invoice
+    ;;
+  *) REDIRECT /invoices
+    ;;
+esac
diff --git a/invoices.css b/invoices.css
new file mode 100755 (executable)
index 0000000..d495fc8
--- /dev/null
@@ -0,0 +1,97 @@
+* {
+  margin: 0; padding: 0;
+  color: inherit; background-color: inherit;
+  font-size: medium; font-weight: normal;
+  text-decoration: none;
+  box-sizing: border-box;
+}
+body {
+  color: black; background-color: white;
+}
+#menu {
+  margin: 0; padding: .5em 1em;
+  border-bottom: 1px solid black;
+}
+#menu a {
+  background-color: #EEF;
+  border: 1px solid black;
+  margin: .5em 1em .5em 0;
+  padding: .125em .5em;
+}
+#menu a:hover {
+  background-color: #FFF;
+  border-color: #888;
+}
+
+.clients .address,
+.senders .address {
+  display: inline-block;
+  background-color: #EEE;
+  white-space: pre;
+  padding: 1em 1em .5em 1em;
+  margin: 1em 0 0 1em;
+  width: 20em;
+}
+.clients .address a,
+.senders .address a,
+a.new {
+  display: block;
+  background-color: #DEF;
+  text-align: center;
+  margin: .5em 3em 0 3em;
+  padding: .125em 1em;
+  border: 1px solid #08F;
+}
+.clients .address a:hover,
+.senders .address a:hover,
+a.new:hover {
+  border-color: #8EF;
+}
+a.new {
+  display: inline-block;
+  margin: 1em;
+}
+
+form textarea,
+form input {
+  padding: .25em .5em;
+  background-color: #FFF;
+  text-align: left;
+}
+.client form,
+.sender form {
+  position: relative;
+  display: inline-block;
+  margin: 1em; padding: 1em;
+  background-color: #EEE;
+  text-align: right;
+}
+.client form textarea,
+.sender form textarea {
+  display: block;
+  min-width: 30em;
+  min-height: 7em;
+  margin-bottom: .5em;
+}
+.sender form textarea {
+  min-height: 13em;
+}
+.client form label[for=hourly] {
+  margin-right: .5em;
+  line-height: 2em;
+  text-align: right;
+  font-weight: bold;
+}
+.client form input#hourly {
+  min-width: 4em; width: 5em;
+  text-align: right;
+}
+.client form button[type=submit],
+.sender form button[type=submit] {
+  display: block;
+  margin: .5em 0 0 auto;
+  padding: .25em .5em;
+  background-color: #DEF;
+  border-radius: .25em;
+  border: 1px solid #08F;
+}