aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--council2007/Votify.pm683
-rw-r--r--council2007/ballot-council200712
-rw-r--r--council2007/officials-council20073
-rw-r--r--council2007/start-council20070
-rw-r--r--council2007/stop-council20070
-rw-r--r--council2007/voters-council2007329
-rw-r--r--council2008/Votify.pm683
-rw-r--r--council2008/ballot-council200819
-rw-r--r--council2008/officials-council20084
-rw-r--r--council2008/start-council20080
-rw-r--r--council2008/stop-council20080
-rw-r--r--council2008/voters-council2008252
-rw-r--r--council2008b/Votify.pm683
-rw-r--r--council2008b/ballot-council2008b6
-rw-r--r--council2008b/officials-council2008b5
-rw-r--r--council2008b/start-council2008b1
-rw-r--r--council2008b/stop-council2008b1
-rw-r--r--council2008b/voters-council2008b245
-rw-r--r--council200906/Votify.pm683
-rw-r--r--council200906/ballot-council20090614
-rw-r--r--council200906/officials-council2009064
-rw-r--r--council200906/start-council2009060
-rw-r--r--council200906/stop-council2009060
-rw-r--r--council200906/voters-council200906248
-rw-r--r--council200912/Votify.pm683
-rw-r--r--council200912/ballot-council2009126
-rw-r--r--council200912/council2009-devs-list260
-rw-r--r--council200912/council2009-devs-list-unsorted260
-rw-r--r--council200912/election-details5
-rw-r--r--council200912/get-devs-list2
-rw-r--r--council200912/officials-council2009125
-rw-r--r--council200912/start-council2009120
-rw-r--r--council200912/stop-council2009120
-rw-r--r--council200912/voters-council200912260
-rw-r--r--council201006/Votify.pm683
-rw-r--r--council201006/ballot-council2010061
-rw-r--r--council201006/council201006-devs-list270
-rw-r--r--council201006/election-details6
-rw-r--r--council201006/get-devs-list2
-rw-r--r--council201006/officials-council2010064
-rw-r--r--council201006/start-council2010060
-rw-r--r--council201006/stop-council2010060
-rw-r--r--council201006/voters-council201006270
-rwxr-xr-xcountify141
l---------current1
-rw-r--r--foundation-referendum-2009-01/Votify.pm683
-rw-r--r--foundation-referendum-2009-01/ballot-foundation-referendum-2009-012
-rw-r--r--foundation-referendum-2009-01/officials-foundation-referendum-2009-014
-rw-r--r--foundation-referendum-2009-01/start-foundation-referendum-2009-010
-rw-r--r--foundation-referendum-2009-01/stop-foundation-referendum-2009-010
-rw-r--r--foundation-referendum-2009-01/voters-foundation-referendum-2009-01211
-rw-r--r--foundation-referendum-2009-01/voters-foundation-referendum-2009-01-init210
-rw-r--r--trustees2008/Votify.pm683
-rw-r--r--trustees2008/ballot-trustees20088
-rw-r--r--trustees2008/officials-trustees20084
-rw-r--r--trustees2008/start-trustees20080
-rw-r--r--trustees2008/stop-trustees20080
-rw-r--r--trustees2008/voters-trustees2008295
-rw-r--r--trustees2009/Votify.pm683
-rw-r--r--trustees2009/ballot-trustees20094
-rw-r--r--trustees2009/officials-trustees20094
-rw-r--r--trustees2009/start-trustees20090
-rw-r--r--trustees2009/stop-trustees20090
-rw-r--r--trustees2009/voters-trustees2009141
-rwxr-xr-xvotify220
65 files changed, 9886 insertions, 0 deletions
diff --git a/council2007/Votify.pm b/council2007/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/council2007/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/council2007/ballot-council2007 b/council2007/ballot-council2007
new file mode 100644
index 0000000..299e5e9
--- /dev/null
+++ b/council2007/ballot-council2007
@@ -0,0 +1,12 @@
+amne
+betelgeuse
+christel
+dberkholz
+dertobi123
+flameeyes
+jaervosz
+jokey
+lu_zero
+uberlord
+vapier
+welp
diff --git a/council2007/officials-council2007 b/council2007/officials-council2007
new file mode 100644
index 0000000..fcdc249
--- /dev/null
+++ b/council2007/officials-council2007
@@ -0,0 +1,3 @@
+fox2mike
+swift
+graaff
diff --git a/council2007/start-council2007 b/council2007/start-council2007
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council2007/start-council2007
diff --git a/council2007/stop-council2007 b/council2007/stop-council2007
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council2007/stop-council2007
diff --git a/council2007/voters-council2007 b/council2007/voters-council2007
new file mode 100644
index 0000000..94c4677
--- /dev/null
+++ b/council2007/voters-council2007
@@ -0,0 +1,329 @@
+aballier
+achumakov
+aetius
+agaffney
+agorf
+agriffis
+ali_bush
+alin
+alonbl
+amne
+anant
+angelos
+anpereir
+antarus
+araujo
+armin76
+aross
+astinus
+augustus
+axxo
+azarah
+bangert
+bass
+batlogg
+battousai
+bbj
+bcowan
+beandog
+betelgeuse
+bicatali
+blackace
+cab
+calchan
+caleb
+cam
+cardoe
+carlo
+caster
+cedk
+centic
+chainsaw
+chiguire
+chrb
+christel
+chtekk
+chutzpah
+cla
+codeman
+codergeek42
+coldwind
+compnerd
+config
+corsair
+cparrott
+cryos
+curtis119
+dams
+dang
+dberkholz
+dcoutts
+deathwing00
+dercorny
+dertobi123
+desultory
+dev-zero
+dholm
+diox
+dirtyepic
+djay
+dju
+dmwaters
+dostrow
+drac
+dragonheart
+drizzt
+dsd
+earthwings
+ehmsen
+eldad
+eradicator
+eva
+falco
+ferdy
+flameeyes
+flammie
+fmccor
+fordfrog
+formula7
+foser
+fox2mike
+fuzzyray
+fvdpol
+g2boojum
+genone
+genstef
+george
+gmsoft
+gongloo
+graaff
+grahl
+gregkh
+griffon26
+grobian
+gurligebis
+gustavoz
+hanno
+hansmi
+hardave
+hattya
+hawking
+hd_brummy
+herbs
+hkbst
+hlieberman
+hoffie
+hollow
+hparker
+humpback
+hyakuhei
+ian
+idani
+ikelos
+iluxa
+jaervosz
+jakub
+je_fro
+jeffw
+jer
+jforman
+jkt
+jmbsvicetto
+jmglov
+joem
+johnm
+joker
+jokey
+josejx
+joshuabaergen
+joslwah
+jrinkovs
+jsin
+jstubbs
+jurek
+kaiowas
+kallamej
+kanaka
+kang
+karltk
+kengland
+keri
+kernelsensei
+kevquinn
+keytoaster
+killerfox
+kingtaco
+klieber
+kloeri
+kolmodin
+kosmikus
+kugelfang
+kumba
+kutsuya
+lack
+langthang
+latexer
+lavajoe
+lcars
+leio
+leonardop
+liquidx
+lisa
+livewire
+lizb
+lordvan
+lu_zero
+lucass
+mabi
+maedhros
+maekke
+malc
+marduk
+marienz
+marineam
+mark_alec
+markm
+markusle
+masterdriverz
+matsuu
+mattam
+mattepiu
+mattm
+mcummings
+mdisney
+merlin
+metalgod
+method
+ming
+mjolnir
+mkay
+moloh
+mr_bones_
+mrness
+muchar
+musikc
+nakano
+neddyseagoon
+nelchael
+nerdboy
+neysx
+nichoj
+nightmorph
+nigoro
+nixnut
+nixphoeni
+nyhm
+omp
+opfer
+pappy
+pauldv
+pbienst
+pclouds
+pebenito
+peitolm
+peper
+philantrop
+phosphan
+phreak
+pilla
+pingu
+pioto
+pjp
+plate
+polvi
+psi29a
+puggy
+pva
+pvdabeel
+py
+pylon
+pyrania
+pythonhead
+r0bertz
+r2d2
+r3pek
+radek
+rajiv
+ramereth
+rane
+ranger
+rbrown
+rbu
+reb
+redhatter
+remi
+ribosome
+rl03
+robbat2
+rocket
+roger55
+s4t4n
+satya
+sbriesen
+scen
+seemant
+sekretarz
+shadoww
+shellsage
+shindo
+sirseoman
+smithj
+solar
+spb
+spock
+squash
+st3vie
+steev
+stefaan
+stkn
+strerror
+suka
+superlag
+swegener
+swift
+tantive
+taviso
+tchiwam
+tercel
+tester
+tgall
+the_paya
+think4urs11
+thoand
+thunder
+ticho
+tocharian
+tomk
+tove
+trapni
+troll
+trombik
+truedfx
+tsunam
+tupone
+twp
+uberlord
+ulm
+usata
+vanquirius
+vapier
+vorlon
+voxus
+voyageur
+weeve
+welp
+williamh
+wltjr
+wolf31o2
+wormo
+wrobel
+wschlich
+xmerlin
+yoswink
+yuval
+yvasilev
+zaheerm
+zeypher
+zmedico
+zypher
+zzam
diff --git a/council2008/Votify.pm b/council2008/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/council2008/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/council2008/ballot-council2008 b/council2008/ballot-council2008
new file mode 100644
index 0000000..fd44753
--- /dev/null
+++ b/council2008/ballot-council2008
@@ -0,0 +1,19 @@
+astinus
+Betelgeuse
+cardoe
+dberkholz
+dertobi123
+dev-zero
+ferdy
+Flameeyes
+fmccor
+Halcy0n
+hkBst
+jer
+Jokey
+leio
+lu_zero
+peper
+ulm
+welp
+zlin
diff --git a/council2008/officials-council2008 b/council2008/officials-council2008
new file mode 100644
index 0000000..17a74fc
--- /dev/null
+++ b/council2008/officials-council2008
@@ -0,0 +1,4 @@
+jmbsvicetto
+rane
+antarus
+root
diff --git a/council2008/start-council2008 b/council2008/start-council2008
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council2008/start-council2008
diff --git a/council2008/stop-council2008 b/council2008/stop-council2008
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council2008/stop-council2008
diff --git a/council2008/voters-council2008 b/council2008/voters-council2008
new file mode 100644
index 0000000..4845b5f
--- /dev/null
+++ b/council2008/voters-council2008
@@ -0,0 +1,252 @@
+aballier
+aetius
+agaffney
+agorf
+ali_bush
+amne
+anant
+angelos
+antarus
+araujo
+armin76
+astinus
+b33fc0d3
+bangert
+bass
+battousai
+bbj
+beandog
+betelgeuse
+bicatali
+blackace
+bluebird
+bunder
+cab
+calchan
+caleb
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+chtekk
+chutzpah
+cla
+codeman
+coldwind
+compnerd
+corsair
+cryos
+dams
+dang
+darkside
+dav_it
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+djay
+dmwaters
+dostrow
+drac
+dragonheart
+drizzt
+dsd
+earthwings
+ehmsen
+elvanor
+eradicator
+eva
+falco
+ferdy
+flameeyes
+flammie
+fmccor
+fordfrog
+fox2mike
+fuzzyray
+g2boojum
+genone
+genstef
+gentoofan23
+george
+gmsoft
+graaff
+grahl
+gregkh
+griffon26
+grobian
+grozin
+gurligebis
+halcy0n
+hanno
+hattya
+haubi
+hawking
+hd_brummy
+hkbst
+hoffie
+hollow
+hparker
+humpback
+ian
+ikelos
+iluxa
+ingmar
+jaervosz
+jakub
+je_fro
+jer
+jforman
+jkt
+jmbsvicetto
+jmglov
+joker
+jokey
+josejx
+joslwah
+jrinkovs
+jsbronder
+jsin
+jurek
+kallamej
+kanaka
+kang
+ken69267
+keri
+kernelsensei
+keytoaster
+killerfox
+kingtaco
+klausman
+klieber
+kolmodin
+kumba
+lack
+lavajoe
+leio
+leonardop
+livewire
+loki_val
+lordvan
+lu_zero
+lucass
+mabi
+maedhros
+maekke
+marineam
+mark_alec
+markm
+markusle
+matsuu
+mattepiu
+mbres
+mdisney
+mduft
+mjf
+moloh
+mpagano
+mr_bones_
+mrness
+mueli
+musikc
+neddyseagoon
+nelchael
+nerdboy
+neysx
+nichoj
+nightmorph
+nixnut
+nixphoeni
+nyhm
+omp
+opfer
+pauldv
+pchrist
+pclouds
+pebenito
+peitolm
+peper
+phosphan
+phreak
+pilla
+pingu
+pjp
+polvi
+pva
+pvdabeel
+py
+pythonhead
+r0bertz
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+redhatter
+remi
+ribosome
+rich0
+ricmm
+rl03
+robbat2
+s4t4n
+sbriesen
+scen
+serkan
+shadow
+shellsage
+shindo
+sirseoman
+smithj
+solar
+spock
+steev
+stefaan
+stkn
+strerror
+suka
+swegener
+swift
+tantive
+tester
+tgall
+tgurr
+the_paya
+think4urs11
+titefleur
+tomk
+tommy
+tove
+trapni
+truedfx
+tsunam
+tupone
+ulm
+vanquirius
+vapier
+vorlon
+voxus
+voyageur
+weaver
+welp
+williamh
+wltjr
+wolf31o2
+wormo
+wrobel
+wschlich
+xmerlin
+yngwin
+yoswink
+yuval
+yvasilev
+zaheerm
+zlin
+zmedico
+zzam
diff --git a/council2008b/Votify.pm b/council2008b/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/council2008b/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/council2008b/ballot-council2008b b/council2008b/ballot-council2008b
new file mode 100644
index 0000000..391fe75
--- /dev/null
+++ b/council2008b/ballot-council2008b
@@ -0,0 +1,6 @@
+dev-zero
+jer
+leio
+ssuominen
+ulm
+_reopen_nominations
diff --git a/council2008b/officials-council2008b b/council2008b/officials-council2008b
new file mode 100644
index 0000000..23ff031
--- /dev/null
+++ b/council2008b/officials-council2008b
@@ -0,0 +1,5 @@
+robbat2
+jmbsvicetto
+antarus
+dberkholz
+root
diff --git a/council2008b/start-council2008b b/council2008b/start-council2008b
new file mode 100644
index 0000000..2e10615
--- /dev/null
+++ b/council2008b/start-council2008b
@@ -0,0 +1 @@
+1227916800
diff --git a/council2008b/stop-council2008b b/council2008b/stop-council2008b
new file mode 100644
index 0000000..a4fe570
--- /dev/null
+++ b/council2008b/stop-council2008b
@@ -0,0 +1 @@
+1228521600
diff --git a/council2008b/voters-council2008b b/council2008b/voters-council2008b
new file mode 100644
index 0000000..c6c4253
--- /dev/null
+++ b/council2008b/voters-council2008b
@@ -0,0 +1,245 @@
+aballier
+aetius
+agaffney
+agorf
+ali_bush
+amne
+anant
+angelos
+antarus
+araujo
+armin76
+b33fc0d3
+bangert
+bass
+battousai
+beandog
+betelgeuse
+bicatali
+blackace
+bluebird
+bunder
+calchan
+caleb
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+chtekk
+chutzpah
+cla
+codeman
+coldwind
+compnerd
+corsair
+cryos
+dams
+dang
+darkside
+dav_it
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+djay
+dmwaters
+dostrow
+dragonheart
+drizzt
+dsd
+earthwings
+ehmsen
+elvanor
+eva
+falco
+fauli
+ferdy
+flameeyes
+flammie
+fmccor
+fordfrog
+ford_prefect
+fox2mike
+fuzzyray
+g2boojum
+gengor
+genone
+genstef
+gentoofan23
+george
+gmsoft
+graaff
+grahl
+gregkh
+griffon26
+grobian
+grozin
+gurligebis
+halcy0n
+hanno
+hattya
+haubi
+hawking
+hd_brummy
+hkbst
+hncaldwell
+hoffie
+hollow
+hparker
+humpback
+i92guboj
+ian
+ikelos
+iluxa
+jaervosz
+jer
+je_fro
+jkt
+jmbsvicetto
+joker
+jokey
+josejx
+joslwah
+jrinkovs
+jsbronder
+jurek
+kallamej
+kalos
+kanaka
+ken69267
+keri
+kernelsensei
+keytoaster
+killerfox
+kingtaco
+klausman
+klieber
+kolmodin
+kumba
+lack
+lavajoe
+leio
+livewire
+loki_val
+lordvan
+lu_zero
+mabi
+maedhros
+maekke
+marineam
+markm
+markusle
+mark_alec
+matsuu
+mattepiu
+mbres
+mdisney
+mduft
+mescalinum
+miknix
+mjf
+moloh
+mpagano
+mrness
+mr_bones_
+mueli
+musikc
+neddyseagoon
+nelchael
+nerdboy
+neurogeek
+neysx
+nichoj
+nightmorph
+nixnut
+nixphoeni
+nyhm
+omp
+patrick
+pauldv
+pchrist
+pebenito
+peitolm
+peper
+phosphan
+pilla
+pingu
+pjp
+polvi
+pva
+pvdabeel
+py
+pythonhead
+quantumsummers
+r0bertz
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+redhatter
+remi
+ribosome
+rich0
+ricmm
+rl03
+robbat2
+s4t4n
+sbriesen
+scarabeus
+scen
+serkan
+shadow
+shellsage
+shindo
+sirseoman
+smithj
+solar
+spock
+ssuominen
+steev
+stefaan
+suka
+swegener
+swift
+tantive
+tcunha
+tester
+tgall
+tgurr
+the_paya
+think4urs11
+timebandit
+titefleur
+tomk
+tommy
+tove
+trapni
+truedfx
+tsunam
+tupone
+ulm
+vanquirius
+vapier
+vorlon
+voxus
+voyageur
+weaver
+welp
+williamh
+wormo
+wrobel
+wschlich
+xmerlin
+yngwin
+yoswink
+yuval
+yvasilev
+zmedico
+zzam
diff --git a/council200906/Votify.pm b/council200906/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/council200906/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/council200906/ballot-council200906 b/council200906/ballot-council200906
new file mode 100644
index 0000000..64e401d
--- /dev/null
+++ b/council200906/ballot-council200906
@@ -0,0 +1,14 @@
+betelgeuse
+calchan
+dertobi123
+dev-zero
+gentoofan23
+leio
+lu_zero
+patrick
+peper
+scarabeus
+solar
+ssuominen
+ulm
+_reopen_nominations
diff --git a/council200906/officials-council200906 b/council200906/officials-council200906
new file mode 100644
index 0000000..bc4323e
--- /dev/null
+++ b/council200906/officials-council200906
@@ -0,0 +1,4 @@
+jmbsvicetto
+rane
+neddyseagoon
+root
diff --git a/council200906/start-council200906 b/council200906/start-council200906
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council200906/start-council200906
diff --git a/council200906/stop-council200906 b/council200906/stop-council200906
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council200906/stop-council200906
diff --git a/council200906/voters-council200906 b/council200906/voters-council200906
new file mode 100644
index 0000000..0a667b8
--- /dev/null
+++ b/council200906/voters-council200906
@@ -0,0 +1,248 @@
+a3li
+aballier
+aetius
+agaffney
+alexxy
+ali_bush
+amne
+angelos
+antarus
+araujo
+arfrever
+armin76
+asn
+b33fc0d3
+bangert
+basic
+bass
+battousai
+beandog
+betelgeuse
+bicatali
+billie
+blackace
+bluebird
+calchan
+caleb
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+chtekk
+chutzpah
+cla
+codeman
+coldwind
+corsair
+cryos
+d2_racing
+dagger
+dams
+dang
+darkside
+dav_it
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+djay
+dmwaters
+dostrow
+dragonheart
+drizzt
+dsd
+earthwings
+elvanor
+eva
+falco
+fauli
+flameeyes
+flammie
+fmccor
+ford_prefect
+fordfrog
+fox2mike
+fuzzyray
+g2boojum
+gbittorrent
+gengor
+genstef
+gentoofan23
+george
+gmsoft
+graaff
+gregkh
+griffon26
+grobian
+grozin
+gurligebis
+halcy0n
+hanno
+hattya
+haubi
+hd_brummy
+hkbst
+hncaldwell
+hoffie
+hollow
+hparker
+humpback
+hwoarang
+i92guboj
+ian
+idl0r
+ikelos
+iluxa
+jaervosz
+je_fro
+jer
+jkt
+jmbsvicetto
+joker
+jokey
+josejx
+jrinkovs
+jsbronder
+jurek
+kallamej
+kalos
+kanaka
+ken69267
+keri
+kernelsensei
+keytoaster
+killerfox
+kingtaco
+klausman
+klieber
+kolmodin
+kumba
+lack
+lavajoe
+leio
+livewire
+loki_val
+lordvan
+lu_zero
+mabi
+maedhros
+maekke
+marineam
+mark_alec
+markm
+markusle
+matsuu
+mattepiu
+mbres
+mdisney
+mduft
+mescalinum
+micm
+miknix
+mpagano
+mr_bones_
+mrness
+mrpouet
+mueli
+musikc
+neddyseagoon
+nelchael
+nerdboy
+neurogeek
+neysx
+nightmorph
+nirbheek
+nixnut
+nixphoeni
+nyhm
+omp
+patrick
+pauldv
+pchrist
+pebenito
+peitolm
+peper
+phosphan
+pilla
+pjp
+polvi
+polynomial-c
+pva
+pvdabeel
+py
+pythonhead
+quantumsummers
+r0bertz
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+redhatter
+remi
+ribosome
+rich0
+ricmm
+rl03
+robbat2
+s4t4n
+sbriesen
+scarabeus
+scen
+serkan
+shadow
+shindo
+sirseoman
+smithj
+solar
+spock
+ssuominen
+steev
+stefaan
+suka
+swegener
+tacotest
+tampakrap
+tantive
+tcunha
+tester
+tgall
+tgurr
+the_paya
+think4urs11
+timebandit
+titefleur
+tomk
+tommy
+tove
+trapni
+truedfx
+tsunam
+tupone
+ulm
+vadimk
+vanquirius
+vapier
+volkmar
+vorlon
+voxus
+voyageur
+weaver
+welp
+williamh
+wormo
+wrobel
+wschlich
+xmerlin
+yngwin
+yoswink
+yuval
+yvasilev
+zmedico
+zzam
diff --git a/council200912/Votify.pm b/council200912/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/council200912/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/council200912/ballot-council200912 b/council200912/ballot-council200912
new file mode 100644
index 0000000..0471032
--- /dev/null
+++ b/council200912/ballot-council200912
@@ -0,0 +1,6 @@
+dev-zero
+patrick
+scarabeus
+tanderson
+wired
+_reopen_nominations
diff --git a/council200912/council2009-devs-list b/council200912/council2009-devs-list
new file mode 100644
index 0000000..5c41357
--- /dev/null
+++ b/council200912/council2009-devs-list
@@ -0,0 +1,260 @@
+a3li
+aballier
+abcd
+aetius
+agaffney
+alexxy
+ali_bush
+amne
+anarchy
+angelos
+antarus
+araujo
+arfrever
+armin76
+asn
+asym
+ayoy
+b33fc0d3
+bangert
+basic
+bass
+battousai
+beandog
+betelgeuse
+bicatali
+billie
+blackace
+bluebird
+calchan
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+chtekk
+chutzpah
+cla
+codeman
+coldwind
+corsair
+craig
+cryos
+d2_racing
+dabbott
+dagger
+dams
+dang
+darkside
+dav_it
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+djay
+djc
+dmwaters
+dostrow
+dragonheart
+drizzt
+dsd
+earthwings
+elvanor
+eva
+falco
+fauli
+flameeyes
+flammie
+fmccor
+ford_prefect
+fordfrog
+fox2mike
+fuzzyray
+g2boojum
+gbittorrent
+gengor
+genstef
+george
+gmsoft
+graaff
+gregkh
+griffon26
+grobian
+grozin
+gurligebis
+halcy0n
+hanno
+hattya
+haubi
+hd_brummy
+hkbst
+hncaldwell
+hoffie
+hollow
+hparker
+humpback
+hwoarang
+i92guboj
+ian
+idl0r
+ikelos
+iluxa
+jaervosz
+je_fro
+jer
+jkt
+jmbsvicetto
+joker
+jokey
+josejx
+jrinkovs
+jsbronder
+jurek
+kallamej
+kanaka
+ken69267
+keri
+kernelsensei
+keytoaster
+killerfox
+kingtaco
+klausman
+klieber
+kolmodin
+lack
+lavajoe
+leio
+livewire
+loki_val
+lordvan
+lu_zero
+lxnay
+mabi
+maedhros
+maekke
+marineam
+mark_alec
+markm
+markusle
+matsuu
+mattepiu
+mbres
+mdisney
+mduft
+mescalinum
+micm
+miknix
+mpagano
+mr_bones_
+mrness
+mrpouet
+mueli
+musikc
+nathanzachary
+neddyseagoon
+nelchael
+nerdboy
+neurogeek
+neysx
+nightmorph
+nirbheek
+nixnut
+nixphoeni
+nyhm
+omp
+pacho
+patrick
+pauldv
+pchrist
+pebenito
+peitolm
+peper
+phajdan.jr
+phosphan
+pilla
+pjp
+polvi
+polynomial-c
+pva
+pvdabeel
+py
+pythonhead
+quantumsummers
+r0bertz
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+redhatter
+remi
+ribosome
+rich0
+ricmm
+rl03
+robbat2
+s4t4n
+sbriesen
+scarabeus
+scen
+serkan
+shadow
+shindo
+sirseoman
+smithj
+solar
+spatz
+sping
+spock
+ssuominen
+steev
+stefaan
+suka
+swegener
+tacotest
+tampakrap
+tanderson
+tantive
+tcunha
+tester
+tgall
+tgurr
+the_paya
+think4urs11
+timebandit
+titefleur
+tomk
+tommy
+tove
+trapni
+truedfx
+tsunam
+tupone
+ulm
+vadimk
+vanquirius
+vapier
+volkmar
+vorlon
+vostorga
+voxus
+voyageur
+weaver
+welp
+williamh
+wired
+wormo
+wrobel
+wschlich
+xmerlin
+yngwin
+yoswink
+yuval
+yvasilev
+zmedico
+zzam
diff --git a/council200912/council2009-devs-list-unsorted b/council200912/council2009-devs-list-unsorted
new file mode 100644
index 0000000..add6664
--- /dev/null
+++ b/council200912/council2009-devs-list-unsorted
@@ -0,0 +1,260 @@
+deathwing00
+polvi
+jkt
+dostrow
+halcy0n
+dberkholz
+cam
+xmerlin
+cryos
+mrness
+carlo
+griffon26
+nixphoeni
+kanaka
+rajiv
+lordvan
+robbat2
+zzam
+pythonhead
+fuzzyray
+chiguire
+wormo
+vanquirius
+gurligebis
+dang
+hparker
+trapni
+hollow
+jaervosz
+cardoe
+wschlich
+genstef
+tgall
+ribosome
+lu_zero
+williamh
+matsuu
+swegener
+voxus
+g2boojum
+gregkh
+josejx
+ian
+bass
+vapier
+chainsaw
+marineam
+yoswink
+ranger
+rl03
+corsair
+battousai
+anarchy
+dams
+flammie
+tomk
+george
+pvdabeel
+amne
+tove
+sbriesen
+s4t4n
+blackace
+iluxa
+jrinkovs
+phosphan
+pauldv
+joker
+dragonheart
+hanno
+humpback
+killerfox
+dsd
+gmsoft
+pjp
+redhatter
+dertobi123
+hattya
+pebenito
+suka
+peitolm
+radek
+fmccor
+truedfx
+flameeyes
+araujo
+mr_bones_
+neysx
+codeman
+agaffney
+tester
+tantive
+solar
+livewire
+nerdboy
+vorlon
+spock
+smithj
+kallamej
+maedhros
+grobian
+rane
+pilla
+stefaan
+steev
+nelchael
+markm
+chtekk
+markusle
+nixnut
+patrick
+jer
+tsunam
+hd_brummy
+earthwings
+pva
+wrobel
+sirseoman
+yvasilev
+chutzpah
+shadow
+jokey
+mark_alec
+antarus
+zmedico
+djay
+tupone
+nightmorph
+kernelsensei
+gbittorrent
+keri
+calchan
+ikelos
+bangert
+beandog
+neddyseagoon
+mattepiu
+the_paya
+kolmodin
+falco
+yuval
+leio
+tacotest
+jmbsvicetto
+caster
+nyhm
+mdisney
+lack
+dev-zero
+je_fro
+aballier
+drizzt
+omp
+mabi
+jurek
+peper
+dirtyepic
+fauli
+shindo
+graaff
+cedk
+remi
+welp
+rbu
+fordfrog
+armin76
+hkbst
+bicatali
+desultory
+ssuominen
+aetius
+voyageur
+think4urs11
+ali_bush
+keytoaster
+scen
+ulm
+musikc
+cla
+r0bertz
+lavajoe
+angelos
+dmwaters
+eva
+coldwind
+py
+mbres
+hoffie
+fox2mike
+dav_it
+maekke
+haubi
+klieber
+betelgeuse
+kingtaco
+ramereth
+tgurr
+mpagano
+titefleur
+jsbronder
+rich0
+elvanor
+yngwin
+ricmm
+ken69267
+klausman
+tommy
+mduft
+weaver
+grozin
+mueli
+darkside
+serkan
+loki_val
+pchrist
+bluebird
+tanderson
+b33fc0d3
+ford_prefect
+neurogeek
+gengor
+quantumsummers
+timebandit
+nathanzachary
+hncaldwell
+miknix
+mescalinum
+scarabeus
+tcunha
+i92guboj
+basic
+alexxy
+a3li
+d2_racing
+tampakrap
+hwoarang
+micm
+nirbheek
+asn
+idl0r
+vadimk
+arfrever
+polynomial-c
+volkmar
+dagger
+billie
+mrpouet
+vostorga
+dabbott
+wired
+abcd
+ayoy
+spatz
+djc
+lxnay
+sping
+asym
+craig
+phajdan.jr
+pacho
diff --git a/council200912/election-details b/council200912/election-details
new file mode 100644
index 0000000..97e0809
--- /dev/null
+++ b/council200912/election-details
@@ -0,0 +1,5 @@
+name: council200912
+dates: 02/01 to 15/01
+officials: jmbsvicetto, NeddySeagoon (missing 3rd official)
+voters: http://www.gentoo.org/proj/en/elections/council/2009/voters-council200912.txt
+ballot: http://www.gentoo.org/proj/en/elections/council/2009/ballot-council200912.txt
diff --git a/council200912/get-devs-list b/council200912/get-devs-list
new file mode 100644
index 0000000..e5144b2
--- /dev/null
+++ b/council200912/get-devs-list
@@ -0,0 +1,2 @@
+ldapsearch '(&(objectClass=gentooDev)(gentooStatus=active)(!(gentooJoin=undefined)))' -Z uid -LLL | grep "^uid" | sed -e "s/uid: //" > council2009-devs-list-unsorted
+sort council2009-devs-list-unsorted > council2009-devs-list
diff --git a/council200912/officials-council200912 b/council200912/officials-council200912
new file mode 100644
index 0000000..2b209d9
--- /dev/null
+++ b/council200912/officials-council200912
@@ -0,0 +1,5 @@
+jmbsvicetto
+neddyseagoon
+fox2mike
+robbat2
+antarus
diff --git a/council200912/start-council200912 b/council200912/start-council200912
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council200912/start-council200912
diff --git a/council200912/stop-council200912 b/council200912/stop-council200912
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council200912/stop-council200912
diff --git a/council200912/voters-council200912 b/council200912/voters-council200912
new file mode 100644
index 0000000..5c41357
--- /dev/null
+++ b/council200912/voters-council200912
@@ -0,0 +1,260 @@
+a3li
+aballier
+abcd
+aetius
+agaffney
+alexxy
+ali_bush
+amne
+anarchy
+angelos
+antarus
+araujo
+arfrever
+armin76
+asn
+asym
+ayoy
+b33fc0d3
+bangert
+basic
+bass
+battousai
+beandog
+betelgeuse
+bicatali
+billie
+blackace
+bluebird
+calchan
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+chtekk
+chutzpah
+cla
+codeman
+coldwind
+corsair
+craig
+cryos
+d2_racing
+dabbott
+dagger
+dams
+dang
+darkside
+dav_it
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+djay
+djc
+dmwaters
+dostrow
+dragonheart
+drizzt
+dsd
+earthwings
+elvanor
+eva
+falco
+fauli
+flameeyes
+flammie
+fmccor
+ford_prefect
+fordfrog
+fox2mike
+fuzzyray
+g2boojum
+gbittorrent
+gengor
+genstef
+george
+gmsoft
+graaff
+gregkh
+griffon26
+grobian
+grozin
+gurligebis
+halcy0n
+hanno
+hattya
+haubi
+hd_brummy
+hkbst
+hncaldwell
+hoffie
+hollow
+hparker
+humpback
+hwoarang
+i92guboj
+ian
+idl0r
+ikelos
+iluxa
+jaervosz
+je_fro
+jer
+jkt
+jmbsvicetto
+joker
+jokey
+josejx
+jrinkovs
+jsbronder
+jurek
+kallamej
+kanaka
+ken69267
+keri
+kernelsensei
+keytoaster
+killerfox
+kingtaco
+klausman
+klieber
+kolmodin
+lack
+lavajoe
+leio
+livewire
+loki_val
+lordvan
+lu_zero
+lxnay
+mabi
+maedhros
+maekke
+marineam
+mark_alec
+markm
+markusle
+matsuu
+mattepiu
+mbres
+mdisney
+mduft
+mescalinum
+micm
+miknix
+mpagano
+mr_bones_
+mrness
+mrpouet
+mueli
+musikc
+nathanzachary
+neddyseagoon
+nelchael
+nerdboy
+neurogeek
+neysx
+nightmorph
+nirbheek
+nixnut
+nixphoeni
+nyhm
+omp
+pacho
+patrick
+pauldv
+pchrist
+pebenito
+peitolm
+peper
+phajdan.jr
+phosphan
+pilla
+pjp
+polvi
+polynomial-c
+pva
+pvdabeel
+py
+pythonhead
+quantumsummers
+r0bertz
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+redhatter
+remi
+ribosome
+rich0
+ricmm
+rl03
+robbat2
+s4t4n
+sbriesen
+scarabeus
+scen
+serkan
+shadow
+shindo
+sirseoman
+smithj
+solar
+spatz
+sping
+spock
+ssuominen
+steev
+stefaan
+suka
+swegener
+tacotest
+tampakrap
+tanderson
+tantive
+tcunha
+tester
+tgall
+tgurr
+the_paya
+think4urs11
+timebandit
+titefleur
+tomk
+tommy
+tove
+trapni
+truedfx
+tsunam
+tupone
+ulm
+vadimk
+vanquirius
+vapier
+volkmar
+vorlon
+vostorga
+voxus
+voyageur
+weaver
+welp
+williamh
+wired
+wormo
+wrobel
+wschlich
+xmerlin
+yngwin
+yoswink
+yuval
+yvasilev
+zmedico
+zzam
diff --git a/council201006/Votify.pm b/council201006/Votify.pm
new file mode 100644
index 0000000..73bc287
--- /dev/null
+++ b/council201006/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/etc/elections/current';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/council201006/ballot-council201006 b/council201006/ballot-council201006
new file mode 100644
index 0000000..e8d662b
--- /dev/null
+++ b/council201006/ballot-council201006
@@ -0,0 +1 @@
+_reopen_nominations
diff --git a/council201006/council201006-devs-list b/council201006/council201006-devs-list
new file mode 100644
index 0000000..532d454
--- /dev/null
+++ b/council201006/council201006-devs-list
@@ -0,0 +1,270 @@
+a3li
+aballier
+abcd
+aetius
+agaffney
+alexxy
+ali_bush
+amne
+anarchy
+angelos
+antarus
+araujo
+arfrever
+armin76
+asn
+asym
+ayoy
+b33fc0d3
+bangert
+basic
+bass
+battousai
+beandog
+betelgeuse
+bicatali
+billie
+blackace
+bluebird
+calchan
+caleb
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+chiiph
+chithanh
+chtekk
+chutzpah
+cla
+codeman
+coldwind
+corsair
+craig
+cryos
+d2_racing
+dabbott
+dagger
+dams
+dang
+darkside
+dav_it
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+djay
+djc
+dmwaters
+dostrow
+dragonheart
+drizzt
+dsd
+earthwings
+elvanor
+eva
+falco
+fauli
+ferringb
+flameeyes
+flammie
+fmccor
+ford_prefect
+fordfrog
+fox2mike
+fuzzyray
+g2boojum
+gengor
+genstef
+george
+gmsoft
+graaff
+gregkh
+griffon26
+grobian
+grozin
+gurligebis
+halcy0n
+hanno
+hattya
+haubi
+hd_brummy
+hkbst
+hncaldwell
+hoffie
+hollow
+hparker
+humpback
+hwoarang
+i92guboj
+ian
+idl0r
+ikelos
+iluxa
+jaervosz
+je_fro
+jer
+jkt
+jlec
+jmbsvicetto
+joker
+jokey
+josejx
+jrinkovs
+jsbronder
+jurek
+kallamej
+kanaka
+ken69267
+keri
+kernelsensei
+keytoaster
+killerfox
+kingtaco
+klausman
+klieber
+kolmodin
+kumba
+lack
+lavajoe
+leio
+livewire
+loki_val
+lordvan
+lu_zero
+lxnay
+mabi
+maedhros
+maekke
+marineam
+mark_alec
+markm
+markusle
+matsuu
+mattepiu
+mbres
+mdisney
+mduft
+mescalinum
+micm
+miknix
+mpagano
+mr_bones_
+mrness
+mrpouet
+mueli
+musikc
+nathanzachary
+neddyseagoon
+nelchael
+nerdboy
+neurogeek
+neysx
+nightmorph
+nimiux
+nirbheek
+nixnut
+nixphoeni
+nyhm
+omp
+pacho
+patrick
+pauldv
+pchrist
+pebenito
+peitolm
+peper
+phajdan.jr
+phosphan
+pilla
+pjp
+polvi
+polynomial-c
+pva
+pvdabeel
+py
+pythonhead
+quantumsummers
+r0bertz
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+reavertm
+redhatter
+remi
+ribosome
+rich0
+ricmm
+rl03
+robbat2
+s4t4n
+sbriesen
+scarabeus
+scen
+serkan
+shadow
+shindo
+sirseoman
+smithj
+sochotnicky
+solar
+spatz
+sping
+spock
+ssuominen
+steev
+stefaan
+suka
+swegener
+tampakrap
+tanderson
+tantive
+tcunha
+tester
+tgall
+tgurr
+the_paya
+think4urs11
+timebandit
+titefleur
+tomjbe
+tomk
+tommy
+tove
+trapni
+truedfx
+tsunam
+tupone
+ulm
+vadimk
+vanquirius
+vapier
+volkmar
+vorlon
+vostorga
+voxus
+voyageur
+weaver
+welp
+williamh
+wired
+wormo
+wrobel
+wschlich
+xarthisius
+xmerlin
+yngwin
+yoswink
+yuval
+yvasilev
+zmedico
+zorry
+zzam
diff --git a/council201006/election-details b/council201006/election-details
new file mode 100644
index 0000000..5ce723c
--- /dev/null
+++ b/council201006/election-details
@@ -0,0 +1,6 @@
+name: council201006
+startDate: 2010-06-20 00:00:00 UTC
+endDate: 2010-07-03 23:59:00 UTC
+officials: neddyseagoon, ulm, tove, robbat2
+voters: http://www.gentoo.org/proj/en/elections/council/2010/voters-council201006.txt
+ballot: http://www.gentoo.org/proj/en/elections/council/2010/ballot-council201006.txt
diff --git a/council201006/get-devs-list b/council201006/get-devs-list
new file mode 100644
index 0000000..ce27f5c
--- /dev/null
+++ b/council201006/get-devs-list
@@ -0,0 +1,2 @@
+ENAME=$(awk '/^name:/{print $2}' election-details)
+ldapsearch '(&(objectClass=gentooDev)(gentooStatus=active)(!(gentooAccess=infra-system.group)))' -Z uid -LLL -S uid | awk '/^uid:/ {print $2}' >${ENAME}-devs-list
diff --git a/council201006/officials-council201006 b/council201006/officials-council201006
new file mode 100644
index 0000000..c7f7376
--- /dev/null
+++ b/council201006/officials-council201006
@@ -0,0 +1,4 @@
+neddyseagoon
+ulm
+tove
+robbat2
diff --git a/council201006/start-council201006 b/council201006/start-council201006
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council201006/start-council201006
diff --git a/council201006/stop-council201006 b/council201006/stop-council201006
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/council201006/stop-council201006
diff --git a/council201006/voters-council201006 b/council201006/voters-council201006
new file mode 100644
index 0000000..ab66a82
--- /dev/null
+++ b/council201006/voters-council201006
@@ -0,0 +1,270 @@
+a3li
+aballier
+abcd
+aetius
+agaffney
+alexxy
+ali_bush
+amne
+anarchy
+angelos
+antarus
+araujo
+arfrever
+armin76
+asn
+asym
+ayoy
+b33fc0d3
+bangert
+basic
+bass
+battousai
+beandog
+betelgeuse
+bicatali
+billie
+blackace
+bluebird
+calchan
+caleb
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+chiiph
+chithanh
+chtekk
+chutzpah
+cla
+codeman
+coldwind
+corsair
+craig
+cryos
+d2_racing
+dabbott
+dagger
+dams
+dang
+darkside
+dav_it
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+djay
+djc
+dmwaters
+dostrow
+dragonheart
+drizzt
+dsd
+earthwings
+elvanor
+eva
+falco
+fauli
+ferringb
+flameeyes
+flammie
+fmccor
+fordfrog
+ford_prefect
+fox2mike
+fuzzyray
+g2boojum
+gengor
+genstef
+george
+gmsoft
+graaff
+gregkh
+griffon26
+grobian
+grozin
+gurligebis
+halcy0n
+hanno
+hattya
+haubi
+hd_brummy
+hkbst
+hncaldwell
+hoffie
+hollow
+hparker
+humpback
+hwoarang
+i92guboj
+ian
+idl0r
+ikelos
+iluxa
+jaervosz
+je_fro
+jer
+jkt
+jlec
+jmbsvicetto
+joker
+jokey
+josejx
+jrinkovs
+jsbronder
+jurek
+kallamej
+kanaka
+ken69267
+keri
+kernelsensei
+keytoaster
+killerfox
+kingtaco
+klausman
+klieber
+kolmodin
+kumba
+lack
+lavajoe
+leio
+livewire
+loki_val
+lordvan
+lu_zero
+lxnay
+mabi
+maedhros
+maekke
+marineam
+mark_alec
+markm
+markusle
+matsuu
+mattepiu
+mbres
+mdisney
+mduft
+mescalinum
+micm
+miknix
+mpagano
+mr_bones_
+mrness
+mrpouet
+mueli
+musikc
+nathanzachary
+neddyseagoon
+nelchael
+nerdboy
+neurogeek
+neysx
+nightmorph
+nimiux
+nirbheek
+nixnut
+nixphoeni
+nyhm
+omp
+pacho
+patrick
+pauldv
+pchrist
+pebenito
+peitolm
+peper
+phajdan.jr
+phosphan
+pilla
+pjp
+polvi
+polynomial-c
+pva
+pvdabeel
+py
+pythonhead
+quantumsummers
+r0bertz
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+reavertm
+redhatter
+remi
+ribosome
+rich0
+ricmm
+rl03
+robbat2
+s4t4n
+sbriesen
+scarabeus
+scen
+serkan
+shadow
+shindo
+sirseoman
+smithj
+sochotnicky
+solar
+spatz
+sping
+spock
+ssuominen
+steev
+stefaan
+suka
+swegener
+tampakrap
+tanderson
+tantive
+tcunha
+tester
+tgall
+tgurr
+the_paya
+think4urs11
+timebandit
+titefleur
+tomjbe
+tomk
+tommy
+tove
+trapni
+truedfx
+tsunam
+tupone
+ulm
+vadimk
+vanquirius
+vapier
+volkmar
+vorlon
+vostorga
+voxus
+voyageur
+weaver
+welp
+williamh
+wired
+wormo
+wrobel
+wschlich
+xarthisius
+xmerlin
+yngwin
+yoswink
+yuval
+yvasilev
+zmedico
+zorry
+zzam
diff --git a/countify b/countify
new file mode 100755
index 0000000..e089584
--- /dev/null
+++ b/countify
@@ -0,0 +1,141 @@
+#!/usr/bin/perl -w
+# $Id: countify,v 1.3 2005/05/16 18:10:46 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# countify: collect, tabulate and announce ballot results
+#
+
+#BEGIN { push @INC, (getpwnam 'fox2mike')[7].'/elections' }
+BEGIN { push @INC, '/etc/elections/current' }
+
+use POSIX;
+use Getopt::Long;
+use List::Util;
+use Votify 'official';
+use strict;
+
+######################################################################
+# Global vars
+######################################################################
+
+(my $zero = $0) =~ s,.*/,,;
+(my $version = '$Revision: 1.3 $') =~ s/.*?(\d.*\d).*/$zero version $1\n/;
+my %opt;
+my $usage = <<EOT;
+
+usage: $zero <command> <election>
+
+where <command> is one of:
+
+ --collect Collect the submitted ballots from home directories
+ --rank Show ranking based on master ballot
+ --help Show this help message
+ --version Show version information
+
+and <election> is one of the elections currently in-progress. The following
+elections are currently open:
+
+ trustees2005
+
+EOT
+
+######################################################################
+# Main
+######################################################################
+
+package main;
+
+# Make sure umask is secure before we do anything
+umask 077;
+
+# Parse the options on the cmdline. Put the short versions first in
+# each optionstring so that the hash keys are created using the short
+# versions. For example, use 'q|qar', not 'qar|q'.
+my ($result) = GetOptions(
+ \%opt,
+ 'collect', # collect the submitted ballots from home directories
+ 'rank', # show the ranking based on a master ballot
+ 'help', # help message
+ 'version', # version information
+);
+if ($opt{'help'} or not %opt) { print STDERR $usage; exit 0 }
+if ($opt{'version'}) { print STDERR $version; exit 0 }
+die "$zero: only one command allowed; use --help for help\n" if 1 < keys %opt;
+die "$zero: election required; use --help for help\n" unless @ARGV == 1;
+
+my ($election) = $ARGV[0];
+my ($vl) = VoterList->new($election);
+
+if ($opt{'collect'}) {
+ my ($ol) = OfficialList->new($election);
+ my ($master) = MasterBallot->new($election, $vl);
+ $master->collect($vl->voters);
+ for my $o ($ol->officials) {
+ my ($uid, $home) = (getpwnam $o)[2,7];
+ mkdir "$home/results-$election";
+ $master->write("$home/results-$election/master-$election");
+ $vl->write("$home/results-$election/confs-$election");
+ chown $uid, -1, "$home/results-$election",
+ "$home/results-$election/master-$election",
+ "$home/results-$election/confs-$election";
+ }
+ exit 0;
+}
+
+if ($opt{'rank'}) {
+ my ($master) = MasterBallot->new($election, $vl);
+ my (@candidates, @winner, @ranked, @ranks);
+ $master->read("$ENV{HOME}/results-$election/master-$election");
+ $master->generate_candidates();
+ @candidates = sort keys %{$master->{'candidates'}};
+
+ while (1) {
+ $master->tabulate();
+
+ # this is a hack :-(
+ #print "\n";
+ for my $r (@ranked) {
+ #print "$r is already ranked\n";
+ for my $c (@candidates) {
+ $master->{'table'}{"$r+$c"} = -1;
+ }
+ $master->{'table'}{"$r+$r"} = '+++';
+ }
+
+ # now display the table
+ print "\n";
+ $master->display_table();
+
+ @winner = $master->cssd();
+ if (@winner == @candidates) {
+ print "\n*** No additional winners to add to the list ***\n";
+ last;
+ }
+
+ push @ranks, [ @winner ];
+ push @ranked, @winner;
+ if (@ranked == @candidates) {
+ print "\n*** Finished ranking candidates ***\n";
+ last;
+ }
+
+ print "\n*** Running another pass to find the next winners... ***\n";
+ }
+
+ print "\nFinal ranked list:\n";
+ print map "@$_\n", @ranks;
+}
+
+__END__
+
+$Log: countify,v $
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+# vim:sw=4 et
diff --git a/current b/current
new file mode 120000
index 0000000..1dcf9bd
--- /dev/null
+++ b/current
@@ -0,0 +1 @@
+council201006 \ No newline at end of file
diff --git a/foundation-referendum-2009-01/Votify.pm b/foundation-referendum-2009-01/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/foundation-referendum-2009-01/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/foundation-referendum-2009-01/ballot-foundation-referendum-2009-01 b/foundation-referendum-2009-01/ballot-foundation-referendum-2009-01
new file mode 100644
index 0000000..3ce46e2
--- /dev/null
+++ b/foundation-referendum-2009-01/ballot-foundation-referendum-2009-01
@@ -0,0 +1,2 @@
+no
+yes
diff --git a/foundation-referendum-2009-01/officials-foundation-referendum-2009-01 b/foundation-referendum-2009-01/officials-foundation-referendum-2009-01
new file mode 100644
index 0000000..46a4cec
--- /dev/null
+++ b/foundation-referendum-2009-01/officials-foundation-referendum-2009-01
@@ -0,0 +1,4 @@
+patrick
+jmbsvicetto
+antarus
+root
diff --git a/foundation-referendum-2009-01/start-foundation-referendum-2009-01 b/foundation-referendum-2009-01/start-foundation-referendum-2009-01
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/foundation-referendum-2009-01/start-foundation-referendum-2009-01
diff --git a/foundation-referendum-2009-01/stop-foundation-referendum-2009-01 b/foundation-referendum-2009-01/stop-foundation-referendum-2009-01
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/foundation-referendum-2009-01/stop-foundation-referendum-2009-01
diff --git a/foundation-referendum-2009-01/voters-foundation-referendum-2009-01 b/foundation-referendum-2009-01/voters-foundation-referendum-2009-01
new file mode 100644
index 0000000..5b0dbc1
--- /dev/null
+++ b/foundation-referendum-2009-01/voters-foundation-referendum-2009-01
@@ -0,0 +1,211 @@
+a3li
+aballier
+abcd
+agaffney
+agriffis
+ali_bush
+amne
+anant
+anpereir
+antarus
+araujo
+armin76
+astinus
+axxo
+azarah
+bangert
+bass
+batlogg
+bcowan
+beandog
+beejay
+bennyc
+betelgeuse
+bicatali
+blackace
+blubber
+bluebird
+calchan
+cam
+cardoe
+carlo
+carpaski
+caster
+cedk
+chainsaw
+chiguire
+ciaranm
+clefebvre.62
+coldwind
+compnerd
+corsair
+cryos
+cshields
+cybersystem
+dabbott
+dang
+darkside
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dholm
+dirtyepic
+djay
+dju
+dmwaters
+dostrow
+dragonheart
+drizzt
+drobbins
+dsd
+edit21
+eradicator
+fauli
+ferdy
+ferringb
+flameeyes
+flammie
+ford_prefect
+fox2mike
+fuzzyray
+g2boojum
+genone
+genstef
+geoman
+george
+gerrynjr
+graaff
+grahl
+gregkh
+grobian
+gustavoz
+halcy0n
+hansmi
+hattya
+hollow
+hparker
+ian
+iggy
+iluxa
+jaervosz
+jakub
+je_fro
+jer
+jforman
+jkt
+jmbsvicetto
+johnm
+joker
+joshuabaergen
+jrinkovs
+jstubbs
+kaiowas
+kallamej
+kanaka
+karltk
+keri
+killerfox
+klieber
+koon
+kugelfang
+kumba
+lack
+langthang
+lanius
+latexer
+leonardop
+liquidx
+lu_zero
+mabi
+maedhros
+malc
+mark_alec
+markusle
+mcummings
+moloh
+mpagano
+mr_bones_
+mrness
+musikc
+nakano
+neddyseagoon
+nelchael
+nerdboy
+neysx
+nichoj
+nightmorph
+nixnut
+nixphoeni
+npmccallum
+nyhm
+omp
+patrick
+pauldv
+pfeifer
+phreak
+pilla
+pjp
+port001
+pva
+pvdabeel
+pylon
+pythonhead
+quantumsummers
+r2d2
+radek
+rajiv
+ramereth
+rane
+ranger
+rbrown
+rbu
+remi
+ribosome
+rich0
+rl03
+robbat2
+roger55
+ronisbr
+rphillips
+seemant
+shadow
+so
+solar
+spb
+spock
+steev
+stefaan
+stkn
+stuart
+swift
+tampakrap
+tanderson
+tantive
+taviso
+tester
+tgall
+the_paya
+thunder
+ticho
+tigger
+tove
+tsunam
+tupone
+twp
+ulm
+usata
+vapier
+vivo
+volkmar
+vorlon
+weeve
+weirdedout
+wltjr
+wormo
+yoswink
+yvasilev
+zhen
+zypher
+zzam
diff --git a/foundation-referendum-2009-01/voters-foundation-referendum-2009-01-init b/foundation-referendum-2009-01/voters-foundation-referendum-2009-01-init
new file mode 100644
index 0000000..7150063
--- /dev/null
+++ b/foundation-referendum-2009-01/voters-foundation-referendum-2009-01-init
@@ -0,0 +1,210 @@
+a3li Alex Legler 0xF3C06469
+aballier Alexis Ballier 0x160F534A
+abcd Jonathan Callen 0x8D2840EA
+agaffney Andrew Gaffney 0x6A2D77EB
+agriffis Aron Griffis 0x20104EB0
+ali_bush Alistair Bush 0xE894970B
+amne Wernfried Haas 0x16E5A780
+anant Anant Narayanan 0xB99B4111
+anpereir Andrés Pereira 0x76B74DAE
+antarus Alec Warner 0xA00637F4
+araujo Luis Francisco Araujo 0xA2C0A9F1
+armin76 Raúl Porcel 0xF6AD3240
+astinus Alex Howells 0xB188A23A
+axxo Thomas Matthijs 0x682A3231
+azarah Martin Schlemmer 0xA68960B6
+bangert Thilo Bangert 0x80390277
+bass Jose Alberto Suarez Lopez 0xF326EC24
+batlogg Jodok Batlogg 0x0E57868B
+bcowan Brad Teaford Cowan 0xB1F16A56
+beandog Steve Dibb 0x96F8EB58
+beejay Benjamin Judas 0xC31DEDD8
+bennyc Benny Chuang 0xDB8FF979
+betelgeuse Petteri Räty 0x8182B0B4
+bicatali Sebastien Fabbro 0x13CB1360
+blackace Nicholas D. Wolfwood 0x0A5F7D12
+blubber Tiemo Kieft 0xE3E9E3A6
+bluebird Friedrich Oslage 0xF989EFA5
+calchan Denis Dupeyron 0xB16C047F
+cam Camille Huot 0x6949514A
+cardoe Doug Klima 0x179106D0
+carlo Carsten Lohrke 0xF18B496F
+carpaski Nicholas Jones 0xB31DFA34
+caster Vlastimil Babka 0x4E61DE84
+cedk Cédric Krier 0x4EA4FF31
+chainsaw Tony Vroon 0xB5058F9A
+chiguire John Christian Stoddart 0x21B3F464
+ciaranm Ciaran McCreesh 0x352D5E11
+clefebvre.62 Christophe LEFEBVRE
+coldwind Santiago M. Mola 0xAAD203B5
+compnerd Saleem Abdulrasool 0x2D5645E5
+corsair Markus Rothe 0x04ABFC77
+cryos Marcus D. Hanwell 0x7D4117BD
+cshields Corey Shields 0xA3041453
+cybersystem Sascha Schwabbauer 0x84EA0C8F
+dabbott David Abbott 0xF01C4ACC
+dang Daniel Gryniewicz 0x5D119EB1
+darkside Jeremy Olexa 0x60F8A50F
+dberkholz Donnie Berkholz 0xB4B5AEDB
+deathwing00 Ioannis Aslanidis 0x47F370A0
+dertobi123 Tobias Scherbaum 0x30C0F005
+desultory Dean Stephens 0xA8C5D9DB
+dev-zero Tiziano Müller 0xAE9C1E30
+dholm David Holm 0x0228A97D
+dirtyepic Ryan Hill 0xF9A40662
+dju Julien Allanos 0x3CA91F19
+dmwaters Deedra M. Waters 0xE4C635CF
+dostrow Daniel Ostrow 0x237C2E05
+dragonheart Daniel Black 0x76677097
+drizzt Timothy Redaelli 0xEB8D3145
+drobbins Daniel Robbins 0xE372614F
+dsd Daniel Drake 0x3795694A
+edit21 James Phipps B1811D4F
+eradicator Jeremy Huddleston 0x5FA03115
+ferdy Fernando J. Pereda 0x60BD28D4
+ferringb Brian Harring 0x8037554D
+flameeyes Diego Pettenò 0x69875563
+flammie Flammie Pirinen 0x07469AA9
+ford_prefect Arun Raghavan 0x29C3E2EC
+fox2mike Shyam Mani 0xFDD0E345
+fuzzyray Paul Varner 0x6B402757
+g2boojum Grant Goodyear 0xE0F65B76
+genone Marius Mauch 0x933B48D7
+genstef Stefan Schweizer 0xE115F4DB
+gentoofan23 Thomas Anderson 0xA071C079
+geoman Stephen Becker 0xB393C856
+george George Shapovalov 0x0AD58A7F
+gerrynjr Gerald J. Normandin Jr. 0xC1DBDF81
+graaff Hans de Graaff 0xFB0878BB
+grahl Jan Hendrik Grahl 0x83A7B496
+gregkh Greg Kroah-Hartman 0xDB2DFB29
+grobian Fabian Groffen 0x1FCCCC42
+gustavoz Gustavo Zacarias 0x10A7FC90
+halcy0n Mark Loeser 0x458BAE84
+hansmi Michael Hanselmann 0x4A51BBEA
+hattya Akinori Hattori 0xEC917A6D
+hollow Benedikt Boehm 0xB5FAF161
+hparker Homer Parker 0x1D524429
+ian Christian Hartmann 0x692A4865
+iggy Brian Jackson 0x45C583E6
+iluxa Ilya Volynets 0x5166BA5A
+jaervosz Sune Kloppenborg Jeppesen 0xC1CEEAB9
+jakub Jakub Moc 0xCEBA3D9E
+je_fro Jeffrey Gardner 0x4A5D8F23
+jer Jeroen Roovers 0xA792A613
+jforman Jeffrey Forman 0x1950DDD4
+jkt Jan Kundrát 0x44722517
+jmbsvicetto Jorge Manuel B. S. Vicetto 0xF544C802
+johnm John Mylchreest 0x9C745515
+joker Christian Birchinger 0x608F6AEB
+joshuabaergen Joshua Baergen 0x6221BC43
+jrinkovs Joe Rinkovsky 0x86E1E83A
+jstubbs Jason Stubbs 0xC93F64FE
+kaiowas Petre Rodan 0x055DEF31
+kallamej Anders Hellgren 0x65FE446E
+kanaka Joel Martin 0xD0BD70B8
+karltk Karl Trygve Kalleberg 0xDCDE7E08
+keri Keri Harris 0x756D6F67
+killerfox René Nussbaumer 0x354BFD9F
+klieber Kurt Lieber 0x27ED2046
+koon Thierry Carrez 0xB6A55F4F
+kugelfang Danny van Dyk 0xDAED53A2
+kumba Joshua Kinard 0x339E0EE2
+lack Jim Ramsay 0x4E952AE8
+langthang Tuan Van 0x6476B2FD
+lanius Heinrich Wendel 0x5B5E7771
+latexer Peter Johanson 0x6EFA3917
+leonardop Leonardo Boshell 0x3AB00DC4
+liquidx Alastair Tse 0x390714F6
+lu_zero Luca Barbato 0x84E90E34
+mabi Matti Bickel 0x4849EC6C
+maedhros Jonathan Coome 0xA46E5974
+malc Malcolm Lashley 0x11C3441D
+mark_alec Mark Kowarsky 0xEC3A5C32
+markusle Markus Dittrich 0x08FD678F
+mcummings Michael Cummings 0x9E7F4E2E
+moloh Michal Kurgan 0x6D452422
+mpagano Mike Pagano 0xB576E4E3
+mr_bones_ Michael Sterrett 0x22B40A2C
+mrness Alin Năstac 0xE9D60F4D
+musikc Christina Fullam 0xC6BA4161
+nakano Masatomo Nakano 0x6C61958A
+neddyseagoon Roy Bamford 0x0D7A312C
+nelchael Krzysiek Pawlik 0xBC555551
+nerdboy Steve Arnold 0x47D46D61
+neysx Xavier Neys 0x7A090902
+nichoj Joshua Nichols 0x8F856A5C
+nightmorph Joshua Saddler 0x132C5725
+nixnut Gysbert Wassenaar 0x0DFD78A3
+nixphoeni Joe Sapp 0x721E3B48
+npmccallum Nathaniel McCallum 0x209B2B1E
+nyhm Tristan Heaven 0x61B3014B
+omp David Shakaryan 0x4B8FE14B
+opfer Christian Faulhammer 0x2B859DE3
+patrick Patrick Lauer 0xE52864CE
+pauldv Paul de Vrieze 0x18D615DB
+pfeifer Jay Pfeifer 0xFD058638
+phreak Christian Heim 0x9A9F68E6
+pilla Mauricio Lima Pilla 0x441C1EB7
+pjp Peter Penkala 0x06627F19
+port001 Ian Leitch 0x60174462
+pva Peter Volkov 0x45926AB3
+pvdabeel Pieter van den Abeele 0xF238673E
+pylon Lars Weiler 0x195165F4
+pythonhead Rob Cakebread 0x96BA679B
+quantumsummers Matthew Summers 0x08789D46
+r2d2 Robert Paskowitz 0xE0C8678A
+radek Radoslaw Stachowiak 0x500EC195
+rajiv Rajiv Aaron Manglani 0x302A3876
+ramereth Lance Albertson 0x27F4B742
+rane Łukasz Damentko 0x7C60CD3A
+ranger Brent Baude 0xA212AD7A
+rbrown Richard Brown 0xC41D5454
+rbu Robert Buchholz 0xFA61D6CA
+remi Remi Cardona 0x901AB08A
+ribosome Olivier Fisette 0x8B703AAA
+rich0 Richard Freeman 0xA6665569
+rl03 Renat Lumpau 0xC6A838DA
+robbat2 Robin H. Johnson 0x34884E85
+roger55 Roger Miliker 0x14E02B9C
+ronisbr Ronan Arraes Jardim Chagas 0x0BDF14AA
+rphillips Ryan Phillips 0x9A11D435
+seemant Seemant Kulleen 0x3458780E
+shadow Damian Kuras 0x846B8E57
+so Stefano Pacella 0xB5330A9B
+solar Ned Ludd 0x1E0A730C
+spb Stephen Bennett 0x652088B8
+spock Michael Januszewski 0xA3947BE6
+steev Stephen Klimaszewski 0x5C4F6F68
+stefaan Stefaan De Roeck 0x4E0995E3
+stkn Stefan Knoblich 0xAE2B614A
+stuart Stuart Herbert 0xF9AFC57C
+swift Sven Vermeulen 0xCDBA2FDB
+tampakrap Theo Chatzimichos 0x57DC0078
+tantive Michael Imhof 0x7EDC47B7
+taviso Tavis Ormandy 0x2690FD71
+tester Olivier Crête 0x3C7462D4
+tgall Tom Gall 0xA0568509
+the_paya Javier Villavicencio 0x09F2FA06
+thunder Damian Florczyk 0x92344129
+ticho Andrej Kacian 0x7CD93FE2
+tigger Rob Holland 0xD91B4729
+tove Torsten Veller 0x9C67CD96
+tsunam Joshua Jackson 0x9860FC4B
+tupone Alfredo Tupone 0x7CF66216
+twp Tom William Payne 0xA18BCCFD
+ulm Ulrich Müller 0x8222EEEC
+usata Mamoru Komachi 0xACE78E88
+vapier Mike Frysinger 0xE837F581
+vivo Francesco Riosa 0xB54F32E0
+volkmar Mounir Lamouri 0xF2D5A6BE
+vorlon Matthias Geerdsen 0xB16A5183
+weeve Jason Wever 0x58A8AB6F
+weirdedout Noel Saliba C6EECFEF8C
+wltjr William Thomson 0xCCD92F26
+wormo Stephanie J. Lockwood-Childs 0xB16E355F
+yoswink José Luis Rivero 0xBFC72A72
+yvasilev Yuri Vasilevski 0x28938E36
+zhen John P. Davis 0xE28141BB
+zypher Marc Hildebrand 0x03556F0E
+zzam Matthias Schwarzott 0x7BD574E7
diff --git a/trustees2008/Votify.pm b/trustees2008/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/trustees2008/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/trustees2008/ballot-trustees2008 b/trustees2008/ballot-trustees2008
new file mode 100644
index 0000000..22f8b37
--- /dev/null
+++ b/trustees2008/ballot-trustees2008
@@ -0,0 +1,8 @@
+ wltjr
+ NeddySeagoon
+ tgall
+ patrick
+ tsunam
+ je_fro
+ fmccor
+ jakub
diff --git a/trustees2008/officials-trustees2008 b/trustees2008/officials-trustees2008
new file mode 100644
index 0000000..b0bec0c
--- /dev/null
+++ b/trustees2008/officials-trustees2008
@@ -0,0 +1,4 @@
+jmbsvicetto
+rane
+rich0
+root
diff --git a/trustees2008/start-trustees2008 b/trustees2008/start-trustees2008
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trustees2008/start-trustees2008
diff --git a/trustees2008/stop-trustees2008 b/trustees2008/stop-trustees2008
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trustees2008/stop-trustees2008
diff --git a/trustees2008/voters-trustees2008 b/trustees2008/voters-trustees2008
new file mode 100644
index 0000000..75edc58
--- /dev/null
+++ b/trustees2008/voters-trustees2008
@@ -0,0 +1,295 @@
+aballier
+agaffney
+agriffis
+alin
+alonbl
+amne
+anant
+anpereir
+antarus
+araujo
+armin76
+aross
+astinus
+axxo
+azarah
+bangert
+bass
+batlogg
+battousai
+bbj
+bcowan
+beandog
+beejay
+bennyc
+betelgeuse
+bicatali
+blackace
+blubber
+calchan
+cam
+cardoe
+carlo
+carpaski
+caster
+cedk
+centic
+chainsaw
+chiguire
+chrb
+christel
+chtekk
+chutzpah
+ciaranm
+codeman
+compnerd
+corsair
+cryos
+cshields
+cybersystem
+dams
+dang
+dberkholz
+dcoutts
+deathwing00
+dercorny
+dertobi123
+desultory
+dev-zero
+dholm
+dirtyepic
+djay
+dju
+dmwaters
+dostrow
+drac
+dragonheart
+drizzt
+drobbins
+dsd
+earthwings
+ehmsen
+eldad
+eradicator
+falco
+ferdy
+ferringb
+flameeyes
+flammie
+fmccor
+fordfrog
+fox2mike
+fuzzyray
+g2boojum
+genone
+genstef
+geoman
+george
+gerrynjr
+gmsoft
+graaff
+grahl
+gregkh
+griffon26
+grobian
+gurligebis
+gustavoz
+halcy0n
+hanno
+hansmi
+hattya
+hd_brummy
+hkbst
+hlieberman
+hollow
+hparker
+humpback
+hyakuhei
+ian
+iggy
+ikelos
+iluxa
+jaervosz
+jakub
+je_fro
+jer
+jforman
+jkt
+jmbsvicetto
+jmglov
+johnm
+joker
+jokey
+josejx
+joshuabaergen
+joslwah
+jrinkovs
+jstubbs
+jurek
+kaiowas
+kallamej
+kanaka
+kang
+karltk
+kengland
+keri
+kernelsensei
+kevquinn
+killerfox
+kingtaco
+klieber
+kloeri
+kolmodin
+koon
+kosmikus
+kugelfang
+kumba
+kutsuya
+lack
+langthang
+lanius
+latexer
+lcars
+leio
+leonardop
+liquidx
+livewire
+lordvan
+lucass
+lu_zero
+mabi
+maedhros
+malc
+marienz
+marineam
+mark_alec
+markm
+markusle
+masterdriverz
+matsuu
+mattepiu
+mcummings
+mdisney
+method
+mjolnir
+mkay
+moloh
+mr_bones_
+mrness
+nakano
+neddyseagoon
+nelchael
+nerdboy
+neysx
+nichoj
+nightmorph
+nixnut
+nixphoeni
+npmccallum
+nyhm
+omp
+opfer
+pappy
+patrick
+pauldv
+pbienst
+pclouds
+pebenito
+peitolm
+peper
+pfeifer
+phosphan
+phreak
+pilla
+pingu
+pjp
+polvi
+port001
+psi29a
+pva
+pvdabeel
+pylon
+pythonhead
+r2d2
+r3pek
+radek
+rajiv
+ramereth
+rane
+ranger
+rbrown
+rbu
+reb
+redhatter
+remi
+ribosome
+rl03
+robbat2
+rocket
+roger55
+rphillips
+s4t4n
+satya
+sbriesen
+seemant
+sejo
+sekretarz
+shadow
+shellsage
+shindo
+sirseoman
+smithj
+so
+solar
+spb
+spock
+spyderous
+steev
+stefaan
+stkn
+strerror
+stuart
+suka
+swegener
+swift
+tantive
+taviso
+tchiwam
+tester
+tgall
+the_paya
+thoand
+thunder
+ticho
+tigger
+tomk
+tove
+trapni
+troll
+truedfx
+tsunam
+tupone
+twp
+usata
+vanquirius
+vapier
+vivo
+vorlon
+voxus
+weeve
+welp
+williamh
+wltjr
+wolf31o2
+wormo
+wrobel
+wschlich
+xmerlin
+yoswink
+yuval
+yvasilev
+zaheerm
+zhen
+zmedico
+zypher
+zzam
diff --git a/trustees2009/Votify.pm b/trustees2009/Votify.pm
new file mode 100644
index 0000000..0c5565d
--- /dev/null
+++ b/trustees2009/Votify.pm
@@ -0,0 +1,683 @@
+# $Id: Votify.pm,v 1.5 2005/05/16 23:58:09 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify.pm: common classes for votify and countify
+#
+
+package Votify;
+
+use POSIX;
+use List::Util;
+use strict;
+
+our ($datadir) = '/home/fox2mike/elections';
+(our $zero = $0) =~ s,.*/,,;
+
+sub import {
+ my ($class, $mode) = @_;
+ $Votify::mode = $mode;
+}
+
+######################################################################
+# OfficialList
+######################################################################
+
+package OfficialList;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ officials => [],
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/officials-$election")
+ or die("failed to open officials file");
+ chomp(@{$self->{'officials'}} = <F>);
+ close(F);
+
+ bless $self, $class;
+ return $self;
+}
+
+sub officials {
+ my ($self) = @_;
+ @{$self->{'officials'}};
+}
+
+######################################################################
+# VoterList
+######################################################################
+
+package VoterList;
+
+sub new {
+ my ($class, $election) = @_;
+ my (@voterlist, $r);
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/confs-$election",
+ filename => '',
+ voters => {}, # confnum => voter
+ confs => {}, # voter => confnum
+ };
+
+ # no point in waiting to load
+ open(F, "<$Votify::datadir/voters-$election")
+ or die("failed to open voters file");
+ chomp(@voterlist = <F>);
+ close(F);
+
+ # assign confirmation numbers randomly
+ for my $v (@voterlist) {
+ do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
+ $self->{'voters'}{$r} = $v;
+ $self->{'confs'}{$v} = $r;
+ }
+
+ unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
+ die("discrepancy deteced in VoterList");
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+sub confs {
+ my ($self) = @_;
+ sort keys %{$self->{'voters'}};
+}
+
+sub voters {
+ my ($self) = @_;
+ sort keys %{$self->{'confs'}};
+}
+
+sub getvoter {
+ my ($self, $conf) = @_;
+ return $self->{'voters'}{$conf};
+}
+
+sub getconf {
+ my ($self, $voter) = @_;
+ return $self->{'confs'}{$voter};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c ($self->confs) {
+ printf F "%04x %s\n", $c, $self->getvoter($c);
+ }
+ close F;
+}
+
+######################################################################
+# MasterBallot
+######################################################################
+
+package MasterBallot;
+
+use Data::Dumper;
+
+sub new {
+ my ($class, $election, $vl) = @_;
+ my ($self) = {
+ election => $election,
+ default_filename => "$Votify::datadir/master-$election",
+ filename => '',
+ voterlist => $vl,
+ ballots => {}, # indexed by conf num
+ candidates => undef, # indexed by long name
+ table => undef, # indexed by row+column
+ };
+
+ bless $self, $class;
+ return $self;
+}
+
+sub collect {
+ my ($self, @voters) = @_;
+ my ($c, $v, $home, @pwentry);
+
+ for my $v (@voters) {
+ unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
+ die "$v does not correspond to any confirmation number";
+ }
+
+ @pwentry = getpwnam($v);
+ unless (@pwentry) {
+ print STDERR "Warning: unknown user: $v\n";
+ next;
+ }
+
+ $home = $pwentry[7];
+ unless (-d $home) {
+ print STDERR "Warning: no directory: $home\n";
+ next;
+ }
+
+ if (-f "$home/.ballot-$self->{election}-submitted") {
+ my ($b) = Ballot->new($self->{'election'});
+ $b->read("$home/.ballot-$self->{election}-submitted");
+ if ($b->verify) {
+ print STDERR "Errors found in ballot: $v\n";
+ next;
+ }
+ $self->{'ballots'}{$c} = $b;
+ }
+ elsif (-f "$home/.ballot-$self->{election}") {
+ print STDERR "Warning: $v did not submit their ballot\n";
+ }
+ }
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ if (-f $filename) {
+ die "$filename already exists; please remove it first";
+ }
+
+ open(F, ">$filename") or die("can't write to $filename");
+ for my $c (sort keys %{$self->{'ballots'}}) {
+ printf F "--------- confirmation %04x ---------\n", $c;
+ print F $self->{'ballots'}{$c}->to_s
+ }
+ close F;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+ my ($election, $entries) = $self->{'election'};
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ open(F, "<$filename") or die("can't read $filename");
+ { local $/ = undef; $entries = <F>; }
+ for my $e (split /^--------- confirmation /m, $entries) {
+ next unless $e; # skip the first zero-length record
+ unless ($e =~ /^([[:xdigit:]]{4}) ---------\n(.*)$/s) {
+ die "error parsing entry:\n$e";
+ }
+ my ($c, $s, $b) = ($1, $2, Ballot->new($election));
+ $b->from_s($s);
+ $self->{'ballots'}{hex($c)} = $b;
+ }
+}
+
+sub generate_candidates {
+ my ($self) = @_;
+ my ($B, @C, $s);
+
+ # nb: would need to scan all the ballots to support write-ins
+ $B = Ballot->new($self->{'election'});
+ $B->populate;
+ @C = sort map $_->[0], @{$B->choices};
+ for my $c (@C) {
+ $s = $c; # in case $c is shorter than 5 chars
+ for (my $i=5; $i<=length($c); $i++) {
+ $s = substr $c, 0, $i;
+ print join(" ", grep(/^$s/, @C)), "\n";
+ last unless grep(/^$s/, @C) > 1;
+ }
+ $self->{'candidates'}{$c} = $s;
+ }
+}
+
+sub tabulate {
+ my ($self) = @_;
+ my (@candidates); # full candidate list
+ my (%table); # resulting table, row.colum where row defeats column
+ $self->{'table'} = \%table;
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @candidates = keys %{$self->{'candidates'}};
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ $table{"$c1+$c2"} = 0;
+ }
+ $table{"$c1+$c1"} = '***';
+ }
+
+ # generate the table first;
+ # walk through the ballots, tallying the rankings expressed by each ballot
+ for my $b (values %{$self->{'ballots'}}) {
+ my (@choices, %ranks);
+
+ #print "looking at ballot:\n", $b->to_s, "\n";
+
+ # first determine the ranking of each candidate. default ranking is
+ # scalar @candidates.
+ @choices = @{$b->choices};
+ @ranks{@candidates} = (scalar @candidates) x @candidates;
+ #print "ranks before determining:\n", Dumper(\%ranks);
+ for (my $i = 0; $i < @choices; $i++) {
+ @ranks{@{$choices[$i]}} = ($i) x @{$choices[$i]};
+ }
+ #print "ranks after determining:\n", Dumper(\%ranks);
+
+ # second add the results of all the pairwise races into our table
+ for my $c1 (@candidates) {
+ for my $c2 (@candidates) {
+ next if $c1 eq $c2;
+ $table{"$c1+$c2"}++ if $ranks{$c1} < $ranks{$c2};
+ }
+ }
+ #print "table after adding:\n";
+ #$self->display_table;
+ }
+}
+
+sub display_table {
+ my ($self) = @_;
+ my (@longnames, @shortnames);
+ my ($minlen, $maxlen, $formatstr) = (0, 4, '');
+
+ $self->generate_candidates unless $self->{'candidates'};
+ @longnames = sort keys %{$self->{'candidates'}};
+ @shortnames = sort values %{$self->{'candidates'}};
+ $minlen = length scalar keys %{$self->{'ballots'}};
+ $minlen = 5 if $minlen < 5;
+
+ # build the format string
+ for my $s (@shortnames) {
+ if (length($s) > $minlen) {
+ $formatstr .= " %" . length($s) . "s";
+ } else {
+ $formatstr .= " %${minlen}s";
+ }
+ }
+ map { $maxlen = length($_) if length($_) > $maxlen } @longnames;
+
+ # prepend the row header; append newline
+ $formatstr = "%${maxlen}s" . $formatstr . "\n";
+
+ # column headers
+ printf $formatstr, '', @shortnames;
+
+ # rows
+ for my $l (@longnames) {
+ printf $formatstr, $l, @{$self->{'table'}}{map "$l+$_", @longnames};
+ }
+}
+
+# utility for cssd
+sub defeats {
+ my ($self, $o1, $o2) = @_;
+ return 0 if $o1 eq $o2;
+ $self->{'table'}{"$o1+$o2"} > $self->{'table'}{"$o2+$o1"};
+}
+
+# utility for cssd
+sub is_weaker_defeat {
+ my ($self, $A, $X, $B, $Y) = @_;
+ die unless $self->defeats($A, $X);
+ die unless $self->defeats($B, $Y);
+ return (
+ $self->{'table'}{"$A+$X"} < $self->{'table'}{"$B+$Y"} or
+ (
+ $self->{'table'}{"$A+$X"} == $self->{'table'}{"$B+$Y"} and
+ $self->{'table'}{"$X+$A"} > $self->{'table'}{"$Y+$B"}
+ )
+ );
+}
+
+sub cssd {
+ my ($self) = @_;
+ my (@candidates);
+
+ @candidates = sort keys %{$self->{'candidates'}};
+
+ while (1) {
+ my (%transitive_defeats);
+ my (@active, @plist);
+
+ ######################################################################
+ # 5. From the list of [undropped] pairwise defeats, we generate a
+ # set of transitive defeats.
+ # 1. An option A transitively defeats an option C if A
+ # defeats C or if there is some other option B where A
+ # defeats B AND B transitively defeats C.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ $transitive_defeats{"$o1+$o2"} = 1 if $self->defeats($o1, $o2);
+ }
+ }
+ for my $i (@candidates) {
+ for my $j (@candidates) {
+ for my $k (@candidates) {
+ if (exists $transitive_defeats{"$j+$i"} and
+ exists $transitive_defeats{"$i+$k"})
+ {
+ $transitive_defeats{"$j+$k"} = 1;
+ }
+ }
+ }
+ }
+
+ ######################################################################
+ # 6. We construct the Schwartz set from the set of transitive
+ # defeats.
+ # 1. An option A is in the Schwartz set if for all options B,
+ # either A transitively defeats B, or B does not
+ # transitively defeat A.
+ print "\n";
+ A: for my $A (@candidates) {
+ for my $B (@candidates) {
+ next if $transitive_defeats{"$A+$B"} or not $transitive_defeats{"$B+$A"};
+ # countify marks entries +++ instead of *** when they've already
+ # been ranked.
+ if ($self->{'table'}{"$A+$A"} eq '***') {
+ print "option $A is eliminated ($B trans-defeats $A, and $A does not trans-defeat $B)\n";
+ }
+ next A;
+ }
+ push @active, $A;
+ }
+ print "the Schwartz set is {", join(", ", @active), "}\n";
+ @candidates = @active;
+
+ ######################################################################
+ # 7. If there are defeats between options in the Schwartz set, we
+ # drop the weakest such defeats from the list of pairwise
+ # defeats, and return to step 5.
+ # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X)
+ # is less than V(B,Y). Also, (A,X) is weaker than (B,Y) if
+ # V(A,X) is equal to V(B,Y) and V(X,A) is greater than V
+ # (Y,B).
+ # 2. A weakest defeat is a defeat that has no other defeat
+ # weaker than it. There may be more than one such defeat.
+ for my $o1 (@candidates) {
+ for my $o2 (@candidates) {
+ push @plist, [ $o1, $o2 ] if $self->defeats($o1, $o2);
+ }
+ }
+ last unless @plist;
+ @plist = sort {
+ return -1 if $self->is_weaker_defeat(@$a, @$b);
+ return +1 if $self->is_weaker_defeat(@$b, @$a);
+ return 0;
+ } @plist;
+ for my $dx (@plist) {
+ my ($o1, $o2) = @$dx;
+ print("$o1+$o2 ",
+ $self->{'table'}{"$o1+$o2"}, " $o2+$o1 ",
+ $self->{'table'}{"$o2+$o1"}, "\n");
+ }
+ my ($o1, $o2) = @{$plist[0]};
+ $self->{'table'}{"$o1+$o2"} = 0;
+ $self->{'table'}{"$o2+$o1"} = 0;
+ }
+
+ ######################################################################
+ # 8. If there are no defeats within the Schwartz set, then the
+ # winner is chosen from the options in the Schwartz set. If
+ # there is only one such option, it is the winner. If there
+ # are multiple options, the elector with the casting vote
+ # chooses which of those options wins.
+ print "\n";
+ if (@candidates > 1) {
+ print "result: tie between options ", join(", ", @candidates), "\n";
+ } else {
+ print "result: option @candidates wins\n";
+ }
+
+ return @candidates;
+}
+
+######################################################################
+# Ballot
+######################################################################
+
+package Ballot;
+
+sub new {
+ my ($class, $election) = @_;
+ my ($self) = {
+ election => $election,
+ filename => '',
+ default_filename => $ENV{'HOME'}."/.ballot-$election",
+ choices => [],
+ };
+
+ # Bless me, I'm a ballot!
+ bless $self, $class;
+ return $self;
+}
+
+sub from_s {
+ my ($self, $s) = @_;
+ my (@choices);
+
+ for (split "\n", $s) {
+ s/#.*//;
+ next unless /\S/;
+ push @choices, [ split(' ', $_) ];
+ }
+ die("No data in string") unless @choices;
+
+ $self->{'choices'} = \@choices;
+}
+
+sub read {
+ my ($self, $filename) = @_;
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Load the data file
+ open(F, "<$filename") or die("couldn't open $filename");
+ { local $/ = undef; $self->from_s(<F>); }
+ close(F);
+}
+
+sub populate {
+ my ($self) = @_;
+ $self->read("$Votify::datadir/ballot-$self->{election}");
+ @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
+}
+
+sub choices {
+ my ($self) = @_;
+ $self->{'choices'};
+}
+
+sub write {
+ my ($self, $filename) = @_;
+
+ if ($Votify::mode ne 'user') {
+ die("we don't write ballots in official mode");
+ }
+
+ $filename ||= $self->{'default_filename'};
+ $self->{'filename'} = $filename;
+
+ # Don't ever overwrite a ballot
+ die("File already exists; please remove $filename\n") if -e $filename;
+
+ # Write the user's ballot
+ open(F, ">$filename") or die "Failed writing $filename";
+ print F <<EOT;
+# This is a ballot for the $self->{election} election.
+# Please rank your choices in order; first choice at the top and last choice at
+# the bottom. You can put choices on the same line to indicate no preference
+# between them. Any choices you omit from this file are implicitly added at the
+# end.
+#
+# When you're finished editing this, the next step is to verify your ballot
+# with:
+#
+# $Votify::zero --verify $self->{election}
+#
+# When that passes and you're satisfied, the final step is to submit your vote:
+#
+# $Votify::zero --submit $self->{election}
+#
+
+EOT
+ for (@{$self->{'choices'}}) { print F "@$_\n"; }
+ close(F);
+}
+
+sub verify {
+ my ($self) = @_;
+ my (%h, $master, %mh);
+ my (@dups, @missing, @extra);
+ my ($errors_found);
+
+ # Load %h from the user's ballot
+ for my $line (@{$self->{'choices'}}) {
+ for my $entry (@$line) {
+ $h{$entry}++;
+ }
+ }
+
+ # Load the master ballot into another hash and compare them.
+ # The master ballots always do one entry per line, making this a little
+ # easier.
+ $master = Ballot->new($self->{'election'});
+ $master->populate;
+ %mh = map(($_->[0] => 1), @{$master->{'choices'}});
+
+ # Check for extra entries (write-ins should be supported in the future)
+ for (keys %h) {
+ push @extra, $_ unless exists $mh{$_};
+ }
+
+ # Check for duplicate entries
+ @dups = grep { $h{$_} > 1 } keys %h;
+
+ # Check for missing entries (not necessarily an error)
+ for (keys %mh) {
+ push @missing, $_ unless exists $h{$_};
+ }
+
+ # Report errors and warnings
+ if (@extra) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some extra entries that are not part of this election. Sorry,
+but write-ins are not (yet) supported. Please remove these from your ballot:
+
+EOT
+ print map "\t$_\n", @extra;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@dups) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot has some duplicate entries. Please resolve these to a single entry
+to avoid ambiguities:
+
+EOT
+ print map "\t$_\n", @dups;
+ print "\n";
+ }
+ $errors_found++;
+ }
+ if (@{$self->{'choices'}} == 0) {
+ if ($Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot doesn't contain any entries. You can start over by first removing
+the existing ballot, then using --new to generate a new ballot. See --help for
+more information.
+
+EOT
+ }
+ $errors_found++;
+ }
+ elsif (@missing and $Votify::mode eq 'user') {
+ print <<EOT;
+Your ballot is missing some entries. This is not an error, but note that these
+will be implied as a final line, with no preference between them, like this:
+
+EOT
+ print "\t", join(" ", @missing), "\n";
+ print "\n";
+ }
+ if ($Votify::mode eq 'user' and !$errors_found and
+ @{$self->{'choices'}} == 1 and
+ scalar(keys %h) == scalar(keys %mh))
+ {
+ print <<EOT;
+Your ballot contains all the candidates on a single line! This means you have
+no preference between the candidates. This is not an error, but note that this
+is a meaningless ballot that will have no effect on the election.
+
+EOT
+ }
+
+ # Stop if there were errors
+ if ($Votify::mode eq 'user' and $errors_found) {
+ print("There were errors found in your ballot.\n");
+ die("Please correct them and try again.\n\n");
+ }
+ return $errors_found;
+}
+
+sub to_s {
+ my ($self) = @_;
+ join '', map "@$_\n", @{$self->{'choices'}};
+}
+
+1;
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+
+__END__
+
+$Log: Votify.pm,v $
+Revision 1.5 2005/05/16 23:58:09 agriffis
+change wording
+
+Revision 1.4 2005/05/16 18:40:07 agriffis
+fix shortname calculation
+
+Revision 1.3 2005/05/16 18:10:46 agriffis
+ranking works completely now, even if it needs badly to be refactored
+
+Revision 1.2 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et
diff --git a/trustees2009/ballot-trustees2009 b/trustees2009/ballot-trustees2009
new file mode 100644
index 0000000..eba0fc2
--- /dev/null
+++ b/trustees2009/ballot-trustees2009
@@ -0,0 +1,4 @@
+musikc
+Patrick
+quantumsummers
+robbat2
diff --git a/trustees2009/officials-trustees2009 b/trustees2009/officials-trustees2009
new file mode 100644
index 0000000..17a74fc
--- /dev/null
+++ b/trustees2009/officials-trustees2009
@@ -0,0 +1,4 @@
+jmbsvicetto
+rane
+antarus
+root
diff --git a/trustees2009/start-trustees2009 b/trustees2009/start-trustees2009
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trustees2009/start-trustees2009
diff --git a/trustees2009/stop-trustees2009 b/trustees2009/stop-trustees2009
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trustees2009/stop-trustees2009
diff --git a/trustees2009/voters-trustees2009 b/trustees2009/voters-trustees2009
new file mode 100644
index 0000000..0e05965
--- /dev/null
+++ b/trustees2009/voters-trustees2009
@@ -0,0 +1,141 @@
+a3li
+aballier
+agaffney
+ali_bush
+amne
+anant
+antarus
+araujo
+armin76
+bangert
+bass
+beandog
+betelgeuse
+bicatali
+blackace
+bluebird
+calchan
+cam
+cardoe
+carlo
+caster
+cedk
+chainsaw
+chiguire
+coldwind
+compnerd
+corsair
+cryos
+dang
+darkside
+dberkholz
+deathwing00
+dertobi123
+desultory
+dev-zero
+dirtyepic
+dmwaters
+dostrow
+dragonheart
+dsd
+eradicator
+fauli
+ferdy
+flameeyes
+flammie
+fmccor
+ford_prefect
+fox2mike
+fuzzyray
+g2boojum
+ge
+genstef
+gentoofan23
+graaff
+grahl
+gregkh
+grobian
+halcy0n
+hattya
+hollow
+hparker
+ian
+iluxa
+jaervosz
+je_fro
+jer
+jkt
+jmbsvicetto
+joker
+jrinkovs
+kallamej
+kanaka
+keri
+killerfox
+klieber
+kumba
+lack
+leonardop
+lu_zero
+mabi
+maedhros
+mark_alec
+markusle
+moloh
+mpagano
+mr_bones_
+mrness
+musikc
+neddyseagoon
+nelchael
+nerdboy
+neysx
+nichoj
+nightmorph
+nixnut
+nixphoeni
+nyhm
+omp
+patrick
+pauldv
+pilla
+pjp
+pva
+pvdabeel
+pythonhead
+quantumsummers
+radek
+rajiv
+ramereth
+rane
+ranger
+rbu
+remi
+ribosome
+rich0
+rl03
+robbat2
+solar
+spock
+steev
+stefaan
+swift
+tantive
+tester
+tgall
+the_paya
+tove
+tsunam
+tupone
+ulm
+vapier
+vorlon
+wormo
+yoswink
+yvasilev
+zzam
+ciaranm
+david
+jay
+philantrop
+seemant
diff --git a/votify b/votify
new file mode 100755
index 0000000..c8d4e40
--- /dev/null
+++ b/votify
@@ -0,0 +1,220 @@
+#!/usr/bin/perl -w
+# $Id: votify,v 1.5 2005/05/16 04:03:46 agriffis Exp $
+#
+# Copyright 2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# votify: generate, verify and submit voting ballots for trustee elections
+#
+
+#BEGIN { push @INC, (getpwnam 'fox2mike')[7].'/elections' }
+BEGIN { push @INC, '/etc/elections/current' }
+
+use POSIX;
+use Getopt::Long;
+use List::Util;
+use Votify 'user';
+use strict;
+
+######################################################################
+# Global vars
+######################################################################
+
+(my $zero = $0) =~ s,.*/,,;
+(my $version = '$Revision: 1.5 $') =~ s/.*?(\d.*\d).*/$zero version $1\n/;
+my (%opt);
+
+# Collect the open elections
+my (@open_elections, $usage_elections);
+opendir(D, "$Votify::datadir/") or die;
+@open_elections = sort grep {
+ s/^start-// and do {
+ my ($starttime) = (stat _)[9] if stat("$Votify::datadir/start-$_");
+ my ($stoptime) = (stat _)[9] if stat("$Votify::datadir/stop-$_");
+ ((not defined $starttime or $starttime < time) and
+ (not defined $stoptime or $stoptime > time))
+ }
+} readdir D;
+closedir D;
+if (@open_elections) {
+ $usage_elections = join("\n ", @open_elections);
+} else {
+ $usage_elections = "(no elections currently open)";
+}
+
+my $usage = <<EOT;
+
+usage: $zero <command> <election>
+
+where <command> is one of:
+
+ --new Generate a new ~/.ballot-<election> for editing
+ --verify Verify content of ~/.ballot-<election>
+ --submit Submit ~/.ballot-<election> to be counted
+ --help Show this help message
+ --version Show version information
+
+and <election> is one of the elections currently in-progress. The following
+elections are currently open:
+
+ $usage_elections
+
+Instructions:
+
+(1) Create a new ballot with the --new command. This generates a file in your
+ home directory called ~/.ballot-<election>. The file contains a shuffled
+ listing of the candidates. Note that the permissions on the file are set so
+ that only you have read+write permissions.
+
+ \$ $zero --new <election>
+
+(2) Edit ~/.ballot-<election> and rearrange the candidates linewise in order of
+ preference. Candidates you consider equal can be listed on the same line.
+ Any candidates you omit are implied on the last line. For example, if tom,
+ jerry, sam, linford and karin are running, you could put:
+
+ karin
+ linford jerry
+
+ In this case, you prefer karin over everybody else. You prefer linford and
+ jerry over tom and sam.
+
+ The file format is case-insensitive, ignores empty lines and comments that
+ start with the hash '#' symbol.
+
+(3) Verify your choices. The $zero program will explain in English if it
+ appears that you've made any errors in your ballot.
+
+ \$ $zero --verify <election>
+
+(4) Submit your ballot. This renames your ballot to
+ ~/.ballot-<election>-submitted so that it will be tallied when the votes
+ are collected.
+
+ \$ $zero --submit <election>
+
+EOT
+
+######################################################################
+# Main
+######################################################################
+
+package main;
+
+# Make sure umask is secure before we do anything
+umask 077;
+
+# Parse the options on the cmdline. Put the short versions first in
+# each optionstring so that the hash keys are created using the short
+# versions. For example, use 'q|qar', not 'qar|q'.
+my ($result) = GetOptions(
+ \%opt,
+ 'new', # generate new ~/.ballot-<election>
+ 'verify', # verify content of ~/.ballot-<election>
+ 'submit', # rename ~/.ballot to ~/.ballot-<election>-submitted
+ 'help', # help message
+ 'version', # version information
+);
+if ($opt{'help'} or not %opt) { print STDERR $usage; exit 0 }
+if ($opt{'version'}) { print STDERR $version; exit 0 }
+die "$zero: only one command allowed; use --help for help\n" if 1 < keys %opt;
+die "$zero: election required; use --help for help\n" unless @ARGV == 1;
+
+my ($election) = $ARGV[0];
+my ($b) = Ballot->new($election);
+
+# Check if the election is open. This should really happen in an
+# Election class in Votify.pm eventually
+my ($starttime, $stoptime);
+if (stat("$Votify::datadir/start-$election")) { $starttime = (stat _)[9] }
+if (stat("$Votify::datadir/stop-$election")) { $stoptime = (stat _)[9] }
+if ($starttime && $starttime > time) {
+ print "\n", "*" x 75, "\n";
+ print "WARNING: Specified election doesn't start until ",
+ scalar(localtime $starttime), "\n";
+ print "*" x 75, "\n";
+}
+if ($stoptime && $stoptime < time) {
+ print "\n", "*" x 75, "\n";
+ print "WARNING: Specified election ended at ",
+ scalar(localtime $stoptime), "\n";
+ print "*" x 75, "\n";
+}
+
+if ($opt{'new'}) {
+ # Make sure the user is eligible
+ open(F, "<$Votify::datadir/voters-$election") or die "Failed to open voters file";
+ my (@voters) = <F>;
+ chomp(@voters);
+ close(F);
+
+ unless (grep { $_ eq getpwuid($>) } @voters) {
+ print STDERR <<EOT;
+
+I'm sorry, you are not a registered voter for this election. Please
+contact one of the election officials if you feel this is in error,
+either on IRC or in email.
+
+EOT
+ exit 1;
+ }
+
+ $b->populate();
+ $b->write();
+
+ # Let the user know what we did
+ print <<EOF;
+
+Welcome to the $election election! Your ballot has been written to
+
+ $b->{filename}
+
+Please edit this file with your preferred editor. Additional instructions can
+be read at the top of the ballot.
+
+EOF
+ exit 0;
+}
+
+if ($opt{'verify'} or $opt{'submit'}) {
+ $b->read();
+ $b->verify();
+ if ($opt{'verify'}) {
+ print <<EOF;
+Your ballot checks out ok, congratulations! Your next step should be
+
+ $zero --submit $election
+
+EOF
+ exit 0;
+ }
+
+ my ($s) = $b->{'filename'}.'-submitted';
+ die("File already exists, please remove $s") if -e $s;
+ rename($b->{'default_filename'}, $s) or die("Couldn't rename $b->{default_filename}");
+ print <<EOF;
+Your ballot has been renamed to $s
+so that it will be counted when the election is over. Please do not remove your
+ballot until the election results have been announced. Thank you for
+participating in the $election election!
+
+EOF
+ exit 0;
+}
+
+__END__
+
+$Log: votify,v $
+Revision 1.5 2005/05/16 04:03:46 agriffis
+add first pass at countify --rank
+
+Revision 1.3 2005/05/09 23:12:02 agriffis
+Add support for registered voters
+
+Revision 1.2 2005/05/05 23:03:46 agriffis
+Fix indentation (and some output as well)
+
+Revision 1.1 2005/05/05 22:05:34 agriffis
+first pass at Gentoo Foundation voting program
+
+# vim:sw=4 et