git: 95fe54732a25 - main - misc/vote: cleanup port

From: Tobias C. Berner <tcberner_at_FreeBSD.org>
Date: Fri, 11 Nov 2022 20:19:01 UTC
The branch main has been updated by tcberner:

URL: https://cgit.FreeBSD.org/ports/commit/?id=95fe54732a25fcf021f52574bb400a862230f129

commit 95fe54732a25fcf021f52574bb400a862230f129
Author:     Tobias C. Berner <tcberner@FreeBSD.org>
AuthorDate: 2022-11-11 20:10:34 +0000
Commit:     Tobias C. Berner <tcberner@FreeBSD.org>
CommitDate: 2022-11-11 20:18:53 +0000

    misc/vote: cleanup port
    
    - fix wrong version number inside script
    
    Reported by:    diizzy
---
 misc/vote/0001-misc-vote-cleanup-port.patch |  55 +++
 misc/vote/Makefile                          |  13 +-
 misc/vote/distinfo                          |   6 +-
 misc/vote/fetch.out                         | 723 ++++++++++++++++++++++++++++
 4 files changed, 791 insertions(+), 6 deletions(-)

diff --git a/misc/vote/0001-misc-vote-cleanup-port.patch b/misc/vote/0001-misc-vote-cleanup-port.patch
new file mode 100644
index 000000000000..2a42eea651b1
--- /dev/null
+++ b/misc/vote/0001-misc-vote-cleanup-port.patch
@@ -0,0 +1,55 @@
+From 33a1a9ccf0df2fc73cd8d85ab4bcdbf585dce94b Mon Sep 17 00:00:00 2001
+From: "Tobias C. Berner" <tcberner@FreeBSD.org>
+Date: Fri, 11 Nov 2022 21:10:34 +0100
+Subject: [PATCH] misc/vote: cleanup port
+
+Reported by:	diizzy
+---
+ misc/vote/Makefile | 12 +++++++++---
+ misc/vote/distinfo |  6 +++---
+ 2 files changed, 12 insertions(+), 6 deletions(-)
+
+diff --git a/misc/vote/Makefile b/misc/vote/Makefile
+index 9f9a4b897479..e851900e94c7 100644
+--- a/misc/vote/Makefile
++++ b/misc/vote/Makefile
+@@ -1,16 +1,22 @@
+ PORTNAME=	vote
+-DISTVERSION=	1.1
++DISTVERSION=	1.2
+ CATEGORIES=	misc
+-MASTER_SITES=	https://codeberg.org/tcberner/${PORTNAME}/archive/
++MASTER_SITES=	https://codeberg.org/tcberner/${PORTNAME}/raw/v${DISTVERSION}/
++DISTFILES=	${PORTNAME}
++DIST_SUBDIR=	vote-${DISTVERSION}
++EXTRACT_ONLY=	
+ 
+ MAINTAINER=	tcberner@FreeBSD.org
+ COMMENT=	Transparent git based voting system
+ 
++LICENSE=	BSD2CLAUSE
++
++NO_ARCH=	yes
+ NO_BUILD=	yes
+ 
+ PLIST_FILES=	bin/vote
+ 
+ do-install:
+-	${INSTALL_SCRIPT} ${WRKDIR}/vote/vote ${STAGEDIR}${PREFIX}/bin
++	${INSTALL_SCRIPT} ${DISTDIR}/${DIST_SUBDIR}/vote ${STAGEDIR}${PREFIX}/bin
+ 
+ .include <bsd.port.mk>
+diff --git a/misc/vote/distinfo b/misc/vote/distinfo
+index d9414c8ce104..05be2b48d93c 100644
+--- a/misc/vote/distinfo
++++ b/misc/vote/distinfo
+@@ -1,3 +1,3 @@
+-TIMESTAMP = 1668195518
+-SHA256 (vote-1.1.tar.gz) = de2ad0d28cf777239fdb7e6c7c44a5dda528283bef6b807ade5ca02246497705
+-SIZE (vote-1.1.tar.gz) = 5196
++TIMESTAMP = 1668197328
++SHA256 (vote-1.2/vote) = 4c0769cd4ea4578f4d72377afd1404ae0cc08cd8008486557aa4f17eadbeca0e
++SIZE (vote-1.2/vote) = 16615
+-- 
+2.38.1
+
diff --git a/misc/vote/Makefile b/misc/vote/Makefile
index 9f9a4b897479..db31d0f28693 100644
--- a/misc/vote/Makefile
+++ b/misc/vote/Makefile
@@ -1,16 +1,23 @@
 PORTNAME=	vote
-DISTVERSION=	1.1
+DISTVERSION=	1.2
 CATEGORIES=	misc
-MASTER_SITES=	https://codeberg.org/tcberner/${PORTNAME}/archive/
+MASTER_SITES=	https://codeberg.org/tcberner/${PORTNAME}/raw/v${DISTVERSION}/
+DISTFILES=	${PORTNAME}
+DIST_SUBDIR=	vote-${DISTVERSION}
+EXTRACT_ONLY=	#
 
 MAINTAINER=	tcberner@FreeBSD.org
 COMMENT=	Transparent git based voting system
+WWW=		https://codeberg.org/tcberner/vote
 
+LICENSE=	BSD2CLAUSE
+
+NO_ARCH=	yes
 NO_BUILD=	yes
 
 PLIST_FILES=	bin/vote
 
 do-install:
-	${INSTALL_SCRIPT} ${WRKDIR}/vote/vote ${STAGEDIR}${PREFIX}/bin
+	${INSTALL_SCRIPT} ${DISTDIR}/${DIST_SUBDIR}/vote ${STAGEDIR}${PREFIX}/bin
 
 .include <bsd.port.mk>
diff --git a/misc/vote/distinfo b/misc/vote/distinfo
index d9414c8ce104..05be2b48d93c 100644
--- a/misc/vote/distinfo
+++ b/misc/vote/distinfo
@@ -1,3 +1,3 @@
-TIMESTAMP = 1668195518
-SHA256 (vote-1.1.tar.gz) = de2ad0d28cf777239fdb7e6c7c44a5dda528283bef6b807ade5ca02246497705
-SIZE (vote-1.1.tar.gz) = 5196
+TIMESTAMP = 1668197328
+SHA256 (vote-1.2/vote) = 4c0769cd4ea4578f4d72377afd1404ae0cc08cd8008486557aa4f17eadbeca0e
+SIZE (vote-1.2/vote) = 16615
diff --git a/misc/vote/fetch.out b/misc/vote/fetch.out
new file mode 100644
index 000000000000..925be6ae4b9c
--- /dev/null
+++ b/misc/vote/fetch.out
@@ -0,0 +1,723 @@
+#!/bin/sh
+
+VERSION=1.0
+
+#
+# Simple git based voting system
+#
+# if $VOTE_DIR is no defined the checkout happens in the shell-script directory
+#
+# To setup a new shared VOTE_DIR, use:
+# freefall> git init --bare /path/to/votes.git
+# local>    git clone freefall:/path/to/votes.git votes
+# local> cd votes
+# local> git checkout -b main
+#    Create the voters file (username:Name <email>)
+# local> git add voters
+# local> git commit -m "Initialize votes directory"
+# local> git push -u origin main:main
+#
+#
+# The mark_* and color_* values can be overriden in ~/.voterc 
+#
+col_reset="\033[0m"
+# Color used to to show the output of shelled-out git calls.
+col_git="\033[32m"
+# Color for highlights within the script
+col_highlight="\033[33m"
+
+mark_ys="👍"
+mark_no="👎"
+mark_ab="💁"
+mark_dn="💤"
+
+mark_op="🔓"
+mark_cl="🔒"
+
+###
+base_dir="$(dirname $(realpath $0))"
+vote_dir="${VOTE_DIR:-${base_dir}/votes}"
+
+###
+text_ys="y"
+text_no="n"
+text_ab="a"
+text_dn="-"
+
+text_op="o"
+text_op="c"
+
+###
+if [ -f ~/.voterc ] ; then
+	. ~/.voterc ;
+fi
+
+###
+__message () {
+	echo -e "$@"
+}
+
+__error () {
+	echo -e "$@" >&2
+	exit 1
+}
+__usage() {
+	__error "No command given. Available commands are:\n\n" \
+		"- help\t\tShow this message:\n" \
+		"\t\t${col_highlight}% vote help${col_reset}\n\n" \
+		"- init\t\tInitialize a repository:\n" \
+		"\t\t${col_highlight}% vote init <url>${col_reset}\n" \
+		"- update\tUpdate the repository:\n" \
+		"\t\t${col_highlight}% vote update${col_reset}\n\n" \
+		"- list\t\tList current ongoing votes:\n" \
+		"\t\t${col_highlight}% vote list${col_reset}\n" \
+		"- all\t\tList all votes:\n" \
+		"\t\t${col_highlight}% vote all${col_reset}\n" \
+		"- info\t\tShow information on a vote:\n" \
+		"\t\t${col_highlight}% vote info <vid>${col_reset}\n\n" \
+		"- create\tCreate a new vote:\n" \
+		"\t\t${col_highlight}% vote create <subject> <duedate> <further description>${col_reset}\n" \
+		"- close\tMark a vote as closed:\n" \
+		"\t\t${col_highlight}% vote close <vid>${col_reset}\n" \
+		"- reopen\tReopen a vote:\n" \
+		"\t\t${col_highlight}% vote reopen <vid>${col_reset}\n\n" \
+		"- changedate\tChange the Due Date to something new\n" \
+		"\t\t${col_highlight}% vote changedate <vid> <date>${col_reset}\n" \
+		"- changesubject\tChange the Subject to something new\n" \
+		"\t\t${col_highlight}% vote changesubject <vid> <subject>${col_reset}\n\n" \
+		"- vote\t\tPlace a vote:\n" \
+		"\t\t${col_highlight}% vote vote <vid> <yes/no/abstain>${col_reset}\n" \
+		"- votefor\tPlace a vote for another person:\n" \
+		"\t\t${col_highlight}% vote votefor <vid> <yes/no/abstain> <voter>${col_reset}\n\n" \
+		"The checkout directory can be overridden using then ${col_highlight}VOTE_DIR${col_reset}\n" \
+		"\n" \
+		"Vote version ${VERSION}."
+}
+
+__want_args() {
+	local count=$1
+	shift
+
+	if [ $# -ne ${count} ] ; then
+		__error "Expected ${count} arguments, but got $#."
+	fi
+}
+
+__username () {
+	__want_args 1 "$@"
+	local vote_dir="$1"
+
+	username=$(git -C ${vote_dir} config user.email | awk -F '@' '{print $1}')
+	if [ $? -eq 0 ] ; then
+		echo "${username}"
+		return 0
+	fi
+
+	__error "Could not evaluate git user name"
+}
+
+__setup_voters() {
+	__want_args 2 "$@"
+	local votes_dir="$1"
+	local voters="$2"
+
+	if [ -d ${votes_dir} ] ; then
+		if [ -f ${voters} ] ; then
+			for voter in $(awk -F ':' '{print $1}' ${voters})  ; do
+				ln -s zzz ${votes_dir}/${voter}
+			done
+		fi
+	fi
+}
+
+# Managing votes
+create_vote () {
+	__want_args 4 "$@"
+	local vote_dir="$1"
+	update_votes "${vote_dir}"
+
+	local subject="$2"
+	local duedate="$3"
+	local description="$4"
+
+	if [ $? -ne 0 ] ; then
+		__error "Could not update votes prior to creation"
+	fi
+
+	local vid=$(__next_vid "${vote_dir}")
+	local voters=$(awk -F ':' 'BEGIN{OFS=":"}{print " ",$0}' ${vote_dir}/voters | column -t -s ':')
+	local commit_message=$(printf "<${vid}> [CREATE] ${subject} (${duedate})\n\n${description}\n\nVoters:\n${voters}\n")
+	echo -e "${col_git}"
+	cd ${vote_dir} && \
+		mkdir -p ${vid} && \
+		cd ${vid} && \
+		echo "${subject}" > subject && \
+		echo "${duedate}" > duedate && \
+		echo "${description} " > description && \
+		mkdir votes && \
+		__setup_voters $(realpath votes) $(realpath ${vote_dir}/voters) && \
+		git add subject duedate description votes && \
+		git commit -m "${commit_message}" && \
+		git push
+	echo -e "${col_reset}"
+
+	__message ""
+	__pretty_print ${vote_dir} ${vid}
+}
+
+change_date () {
+	__want_args 3 "$@"
+	local vote_dir="$1"
+	update_votes "${vote_dir}"
+	local vid=$(__make_vid "$2")
+	local newdate="$3"
+
+	open=$(__is_open_vote ${vote_dir} ${vid})
+	if [ $? -ne 0 ] ; then
+		__error "Cannot modify a closed vote. ${vote_dir} ${vid}"
+	fi
+	local olddate=$(cat ${vote_dir}/${vid}/duedate)
+
+	local commit_message=$(printf "<${vid}> [CHANGE DATE] ${subject} (${olddate}->${newdate})\n")
+	echo -e "${col_git}"
+	cd ${vote_dir} && \
+		cd ${vid} && \
+		echo "${newdate}" > duedate && \
+		git add duedate && \
+		git commit -m "${commit_message}" && \
+		git push
+	echo -e "${col_reset}"
+
+	list_votes ${vote_dir}
+}
+
+change_subject () {
+	__want_args 3 "$@"
+	local vote_dir="$1"
+	update_votes "${vote_dir}"
+	local vid=$(__make_vid "$2")
+	local newsubject="$3"
+
+	open=$(__is_open_vote ${vote_dir} ${vid})
+	if [ $? -ne 0 ] ; then
+		__error "Cannot modify a closed vote. ${vote_dir} ${vid}"
+	fi
+	local oldsubject=$(cat ${vote_dir}/${vid}/subject)
+
+	local commit_message=$(printf "<${vid}> [CHANGE DATE] ${oldsubject} -> ${newsubject}\n")
+	echo -e "${col_git}"
+	cd ${vote_dir} && \
+		cd ${vid} && \
+		echo "${newsubject}" > subject && \
+		git add subject && \
+		git commit -m "${commit_message}" && \
+		git push
+	echo -e "${col_reset}"
+
+	list_votes ${vote_dir}
+}
+
+
+info_vote () {
+	__want_args 2 "$@"
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	__pretty_print ${vote_dir} ${vid}
+}
+
+__is_open_vote () {
+	__want_args 2 "$@"
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	if [ -f ${vote_dir}/${vid}/closeddate ] ; then
+		return 1
+	fi
+	return 0
+}
+
+__set_vote() {
+	__want_args 4 "$@"
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+	local username="$3"
+	local value="$4"
+
+	open=$(__is_open_vote ${vote_dir} ${vid})
+	if [ $? -ne 0 ] ; then
+		__error "Cannot vote on a closed vote. ${vote_dir} ${vid}"
+	fi
+
+	if [ ! -L ${vote_dir}/${vid}/votes/${username} ] ; then
+		__error "Invalid voter ${username}"
+	fi
+
+	local target=""
+	case "${value}" in
+		"yes")		target="yes"     ;;
+		"no")		target="no"      ;;
+		"abstain")	target="abstain" ;;
+		*)		__error "Invalid vote '${value}' (valid: yes, no, abstain)" ;;
+	esac
+
+	result=$(cd ${vote_dir}/${vid}/votes && ln -sf ${value} ${username})
+	return $?
+}
+
+__short_result () {
+	__want_args 2 "$@"
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	echo $(__tally_vote ${vote_dir} ${vid})
+	return 0
+}
+__pretty_print () {
+	__want_args 2 "$@"
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	local subject=$(cat ${vote_dir}/${vid}/subject)
+
+	local y_voters=""
+	local n_voters=""
+	local a_voters=""
+	local d_voters=""
+	for vote in $(find -s ${vote_dir}/${vid}/votes -type l) ; do
+		value=$(readlink ${vote})
+		voter=$(basename ${vote})
+		case "${value}" in
+			"yes")
+				y_voters="${y_voters}${y_voters:+, }${voter}"
+				;;
+			"no")
+				n_voters="${n_voters}${n_voters:+, }${voter}"
+				;;
+			"abstain")
+				a_voters="${a_voters}${a_voters:+, }${voter}"
+				;;
+			"zzz")
+				d_voters="${d_voters}${d_voters:+, }${voter}"
+				;;
+			*)	   __error "Invalid vote value ${value} in ${vote} (${vid})"
+		esac
+	done
+
+	result=$(printf "Vote on ${subject}.\n\nYes:\t\t${y_voters}\nNo:\t\t${n_voters}\nAbstained:\t${a_voters}\n\nDid not vote:\t${d_voters}")
+
+	echo "${result}"
+	return 0
+}
+
+reopen_vote () {
+	__want_args 2 "$@"
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	open=$(__is_open_vote ${vote_dir} ${vid})
+	if [ $? -eq 0 ] ; then
+		__error "Vote already open"
+	fi
+
+	local closeddate=$(cat ${vote_dir}/${vid}/closeddate)
+	rm ${vote_dir}/${vid}/closeddate
+
+	local tally=$(__short_result ${vote_dir} ${vid})
+	local pretty=$(__pretty_print ${vote_dir} ${vid})
+	local commit_message=$(printf "<${vid}> [REOPEN] ${tally}\n\n${pretty}\nOld Closed Date:\t${closeddate}")
+
+	echo -e "${col_git}"
+	cd ${vote_dir} && \
+		git add ${vote_dir}/${vid}/closeddate && \
+		git commit -m "${commit_message}" && \
+		git push
+	echo -e "${col_reset}"
+
+	__pretty_print ${vote_dir} ${vid}
+	return 0
+}
+
+close_vote () {
+	__want_args 2 "$@"
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	open=$(__is_open_vote ${vote_dir} ${vid})
+	if [ $? -ne 0 ] ; then
+		__error "Vote already closed"
+	fi
+
+	echo $(date -u '+%Y%m%d') > ${vote_dir}/${vid}/closeddate
+
+	local tally=$(__short_result ${vote_dir} ${vid})
+	local pretty=$(__pretty_print ${vote_dir} ${vid})
+	local commit_message=$(printf "<${vid}> [CLOSE] ${tally}\n\n${pretty}")
+
+	echo -e "${col_git}"
+	cd ${vote_dir} && \
+		git add ${vote_dir}/${vid}/closeddate && \
+		git commit -m "${commit_message}" && \
+		git push
+	echo -e "${col_reset}"
+
+	__pretty_print ${vote_dir} ${vid}
+	return 0
+}
+
+# Interacting with votes
+init_votes () {
+	__want_args 2 "$@"
+	local vote_dir="$1"
+	local url="$2"
+
+	if [ -d "${vote_dir}" ] ; then
+		__error "Directory '${vote_dir}' already exists."
+	fi
+
+	echo -e "${col_git}"
+	git clone ${url} ${vote_dir} 
+	if [ $? -ne 0 ] ; then
+		echo -e "${col_reset}"
+		__error "Could not clone ${url} to ${vote_dir}"
+	fi
+	echo -e "${col_reset}"
+
+	list_all_votes ${vote_dir}
+	return 0
+}
+
+update_votes () {
+	__want_args 1 "$@"
+
+	local vote_dir="$1"
+	__check_git ${vote_dir}
+	if [ $? -ne 0 ] ; then
+		__error "Directory '${vote_dir}' is not a repository."
+	fi
+
+	echo -e "${col_git}"
+	cd "${vote_dir}" && git pull --rebase --autostash
+	if [ $? -ne 0 ] ; then
+		echo -e "${col_reset}"
+		__error "Failed to update '${vote_dir}' -- please try manual merge."
+	fi
+	echo -e "${col_reset}"
+
+	list_votes ${vote_dir}
+
+	return 0
+}
+
+__list_vids () {
+	local vote_dir="$1"
+
+	vids=$(find -E "${vote_dir}" -type d -regex ".*/[0-9][0-9][0-9][0-9]$" | sort | awk -F '/' '{print $NF}')
+
+	if [ $? -ne 0 ] ; then
+		__error "Failed to enumerate vids"
+	fi
+
+	echo "${vids}"
+	return 0
+}
+
+__last_vid () {
+	local vote_dir="$1"
+	result=$(__list_vids "${vote_dir}" | tail -n1)
+	echo ${result}
+}
+
+__make_vid () {
+	__want_args 1 "$@"
+	echo $(printf "%04d" $(expr $1 + 0))
+	return 0
+}
+
+__next_vid () {
+	__want_args 1 "$@"
+
+	local vote_dir="$1"
+	last=$(__last_vid "${vote_dir}")
+	if [ -z ${last} ] ; then
+		last=0
+	fi
+
+	echo $(__make_vid $(expr ${last} + 1))
+	return 0
+}
+
+__list_vote_entry () {
+	__want_args 3 "$@"
+
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+	local include_closed="$3"
+
+	if [ ! -d ${vote_dir}/${vid} ] ; then
+		__error "Failed to read info on ${vid} from ${vote_dir}"
+	fi
+
+	local state=${mark_cl}
+	__is_open_vote ${vote_dir} ${vid}
+	if [ $? -eq 0 ] ; then
+		state=${mark_op}
+	fi
+	local duedate=$(cat         ${vote_dir}/${vid}/duedate)
+	local subject=$(cut -c 1-50 ${vote_dir}/${vid}/subject)
+	local votes=$(__tally_vote  ${vote_dir} ${vid})
+	local own_vote=$(__own_vote ${vote_dir} ${vid})
+	local closed_info=""
+	if [ "${include_closed}" = "yes" ] ; then
+		if [ -f ${vote_dir}/${vid}/closeddate ] ; then
+			closed_info="|$(cat ${vote_dir}/${vid}/closeddate)"
+		else
+			closed_info="|"
+		fi
+	fi
+	echo "${state}|${duedate}|${vid}|${subject}|${votes}|${own_vote}${closed_info}"
+}
+
+__vote_mark () {
+	__want_args 1 "$@"
+
+	local result=${mark_dn}
+	case "$1" in
+		"yes")     result=${mark_ys} ;;
+		"no")      result=${mark_no} ;;
+		"abstain") result=${mark_ab} ;;
+	esac
+	echo "${result}"
+}
+
+__vote_text() {
+	__want_args 1 "$@"
+
+	local result=${text_dn}
+	case "$1" in
+		"yes")     result=${text_ys} ;;
+		"no")      result=${text_no} ;;
+		"abstain") result=${text_ab} ;;
+	esac
+	echo "${result}"
+}
+
+__get_vote () {
+	__want_args 3 "$@"
+
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+	local username="$3"
+
+	if [ ! -d ${vote_dir}/${vid} ] ; then
+		__error "Failed to read info on ${vid} from ${vote_dir}"
+	fi
+
+	local uservote=""
+
+	local vote=${vote_dir}/${vid}/votes/${username}
+	voted=1
+
+	local result=${mark_dn}
+	if [ -L ${vote} ] ; then
+		vote_value=$(readlink ${vote})
+		if [ "${vote_value}" != "zzz" ] ; then
+			voted=0
+		fi
+		result=$(__vote_mark ${vote_value})
+	fi
+
+	echo "${result}"
+	return ${voted}
+}
+
+__own_vote () {
+	__want_args 2 "$@"
+
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	local username=$(__username ${vote_dir})
+	local result=$(__get_vote ${vote_dir} ${vid} ${username})
+
+	echo "${result}"
+	return 0
+}
+
+
+__tally_vote () {
+	__want_args 2 "$@"
+
+	local vote_dir="$1"
+	local vid=$(__make_vid "$2")
+
+	if [ ! -d ${vote_dir}/${vid} ] ; then
+		__error "Failed to read info on ${vid} from ${vote_dir}"
+	fi
+
+	local count_yes=0
+	local count_no=0
+	local count_abstain=0
+	local count_dnv=0
+
+	for vote in $(find ${vote_dir}/${vid}/votes -type l) ; do
+		value=$(readlink ${vote})
+		voter=$(basename ${vote})
+		case "${value}" in
+			"yes")     count_yes=$(expr ${count_yes} + 1)         ;;
+			"no")      count_no=$(expr ${count_no} + 1)           ;;
+			"abstain") count_abstain=$(expr ${count_abstain} + 1) ;;
+			"zzz")     count_dnv=$(expr ${count_dnv} + 1)         ;;
+			*)	   __error "Invalid vote value ${value} in ${vote} (${vid})"
+		esac
+	done
+
+	result=$(printf "${mark_ys}%2d, ${mark_no}%2d, ${mark_ab}%2d, ${mark_dn}%2d" ${count_yes} ${count_no} ${count_abstain} ${count_dnv})
+	echo "${result}"
+
+	return 0
+}
+
+list_votes () {
+	__want_args 1 "$@"
+	local vote_dir="$1"
+
+	result=""
+	for vid in $(__list_vids "$@") ; do
+		__is_open_vote ${vote_dir} ${vid}
+		if [ $? -eq 0 ] ; then
+			result="${result}\n$(__list_vote_entry ${vote_dir} ${vid} no)"
+		fi
+	done
+
+	result=$(echo -e "${result}" | sort -r)
+	result=$(printf "State|Due Date|VID|Subject|Tally|Own Vote\n${result}")
+	echo "${result}" | column -t -s '|'
+}
+
+
+list_all_votes () {
+	__want_args 1 "$@"
+	local vote_dir="$1"
+
+	result=""
+	for vid in $(__list_vids "$@") ; do
+		result="${result}\n$(__list_vote_entry ${vote_dir} ${vid} yes)"
+	done
+
+	result=$(echo -e "${result}" | sort -r)
+	result=$(printf "State|Due Date|VID|Subject|Tally|Own Vote|Closed On\n${result}")
+	echo "${result}" | column -t -s '|'
+}
+
+place_vote () {
+	__vote "$@"
+}
+
+place_vote_for () {
+	__vote_for "$@"
+}
+
+__vote_for () {
+	__want_args 4 "$@"
+	local vote_dir="$1"
+	update_votes "${vote_dir}"
+
+	local vid=$(__make_vid "$2")
+	local value="$3"
+	local username="$4"
+
+	if [ ! -L ${vote_dir}/${vid}/votes/${username} ] ; then
+		__error "Invalid voter ${username}"
+	fi
+
+	local author=$(awk -F : "/${username}/{print \$NF}" ${vote_dir}/voters)
+	if [ -z "${author}" ] ; then
+		__error "Could not read author for ${username}"
+	fi
+
+	local action=""
+	local msg=""
+	oldvote=$(__get_vote ${vote_dir} ${vid} ${username})
+	if [ $? -eq 0 ] ; then
+		action="[CHANGE VOTE]"
+		message="${oldvote} -> $(__vote_text ${value})"
+	else
+		action="[SET VOTE]"
+		message="$(__vote_text ${value})"
+	fi
+
+	local foreignvote=""
+	gituser=$(__username ${vote_dir})
+	if [ ${username} != ${gituser} ] ; then
+		foreignvote=" (vote placed by ${gituser})"
+	fi
+
+	__set_vote "${vote_dir}" "${vid}" "${username}" "${value}"
+	if [ $? -ne 0 ] ; then
+		__error "Could not place vote ${value} on ${vid}"
+	fi
+
+	local subject=$(cut -c 1-50 ${vote_dir}/${vid}/subject)
+
+	local commit_message=$(printf "<${vid}> ${action} ${subject} :: ${username} ${message}${foreignvote}\n\n$(__pretty_print ${vote_dir} ${vid})")
+
+	echo -e "${col_git}"
+	cd ${vote_dir} && \
+		git add ${vid}/votes/${username} && \
+		git commit -m "${commit_message}" --author="${author}" && \
+		git push
+	echo -e "${col_reset}"
+
+	__message ""
+	__pretty_print ${vote_dir} ${vid}
+}
+
+
+__vote () {
+	__want_args 3 "$@"
+	local username=$(__username ${vote_dir})
+	if [ -z "${username}" ] ; then
+		__error "Could not read your git username"
+	fi
+	__vote_for "$@" ${username}
+}
+
+# utils
+__check_git () {
+	local git_dir=$(realpath "$1")
+	if [ ! -d "${git_dir}" ] ; then
+		return 1
+	fi
+	(
+		cd "${git_dir}"
+		git rev-parse --is-inside-work-tree > /dev/null 2>&1
+		if [ $? -ne 0 ] ; then
+			return 1
+		fi
+	)
+	return 0
+}
+
+if [ -z $1 ] ; then
+	__usage
+fi
+
+command=$1
+shift
+case "${command}" in
+	"help")    __usage ;;
+	"init")    init_votes      ${vote_dir} "$@" ;;
+	"update")  update_votes    ${vote_dir}      ;;
+	"list")    list_votes      ${vote_dir}      ;;
+	"all")     list_all_votes  ${vote_dir}      ;;
+	"create")  create_vote     ${vote_dir} "$@" ;;
+	"info")    info_vote       ${vote_dir} "$@" ;;
+	"close")   close_vote      ${vote_dir} "$@" ;;
+	"reopen")  reopen_vote     ${vote_dir} "$@" ;;
+	"changedate")    change_date     ${vote_dir} "$@" ;;
+	"changesubject") change_subject  ${vote_dir} "$@" ;;
+	"vote")    place_vote      ${vote_dir} "$@" ;;
+	"votefor") place_vote_for  ${vote_dir} "$@" ;;
+	*)         __usage ;;
+esac