/[gentoo-src]/votify/Votify.pm
Gentoo

Contents of /votify/Votify.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (hide annotations) (download) (as text)
Sun May 15 03:25:41 2005 UTC (13 years, 4 months ago) by agriffis
Branch: MAIN
File MIME type: text/x-perl
add Votify.pm and first cut at countify

1 agriffis 1.1 # $Id: votify,v 1.3 2005/05/09 23:12:02 agriffis Exp $
2     #
3     # Copyright 2005 Gentoo Foundation
4     # Distributed under the terms of the GNU General Public License v2
5     #
6     # votify.pm: common classes for votify and countify
7     #
8    
9     package Votify;
10    
11     use POSIX;
12     use List::Util;
13     use strict;
14    
15     our ($datadir) = '/home/agriffis/elections';
16     (our $zero = $0) =~ s,.*/,,;
17    
18     sub import {
19     my ($class, $mode) = @_;
20     $Votify::mode = $mode;
21     }
22    
23     ######################################################################
24     # OfficialList
25     ######################################################################
26    
27     package OfficialList;
28    
29     sub new {
30     my ($class, $election) = @_;
31     my ($self) = {
32     election => $election,
33     officials => [],
34     };
35    
36     # no point in waiting to load
37     open(F, "<$Votify::datadir/officials-$election")
38     or die("failed to open officials file");
39     chomp(@{$self->{'officials'}} = <F>);
40     close(F);
41    
42     bless $self, $class;
43     return $self;
44     }
45    
46     sub officials {
47     my ($self) = @_;
48     @{$self->{'officials'}};
49     }
50    
51     ######################################################################
52     # VoterList
53     ######################################################################
54    
55     package VoterList;
56    
57     sub new {
58     my ($class, $election) = @_;
59     my (@voterlist, $r);
60     my ($self) = {
61     election => $election,
62     default_filename => "$Votify::datadir/confs-$election",
63     filename => '',
64     voters => {}, # confnum => voter
65     confs => {}, # voter => confnum
66     };
67    
68     # no point in waiting to load
69     open(F, "<$Votify::datadir/voters-$election")
70     or die("failed to open voters file");
71     chomp(@voterlist = <F>);
72     close(F);
73    
74     # assign confirmation numbers randomly
75     for my $v (@voterlist) {
76     do { $r = int rand 0xffff } while exists $self->{'voters'}{$r};
77     $self->{'voters'}{$r} = $v;
78     $self->{'confs'}{$v} = $r;
79     }
80    
81     unless (keys %{$self->{'voters'}} == keys %{$self->{'confs'}}) {
82     die("discrepancy deteced in VoterList");
83     }
84    
85     bless $self, $class;
86     return $self;
87     }
88    
89     sub confs {
90     my ($self) = @_;
91     sort keys %{$self->{'voters'}};
92     }
93    
94     sub voters {
95     my ($self) = @_;
96     sort keys %{$self->{'confs'}};
97     }
98    
99     sub getvoter {
100     my ($self, $conf) = @_;
101     return $self->{'voters'}{$conf};
102     }
103    
104     sub getconf {
105     my ($self, $voter) = @_;
106     return $self->{'confs'}{$voter};
107     }
108    
109     sub write {
110     my ($self, $filename) = @_;
111    
112     $filename ||= $self->{'default_filename'};
113     $self->{'filename'} = $filename;
114    
115     if (-f $filename) {
116     die "$filename already exists; please remove it first";
117     }
118    
119     open(F, ">$filename") or die("can't write to $filename");
120     for my $c ($self->confs) {
121     printf F "%04x %s\n", $c, $self->getvoter($c);
122     }
123     close F;
124     }
125    
126     ######################################################################
127     # MasterBallot
128     ######################################################################
129    
130     package MasterBallot;
131    
132     sub new {
133     my ($class, $election, $vl) = @_;
134     my ($self) = {
135     election => $election,
136     default_filename => "$Votify::datadir/master-$election",
137     filename => '',
138     voterlist => $vl,
139     full => {}, # indexed by conf num
140     };
141    
142     bless $self, $class;
143     return $self;
144     }
145    
146     sub collect {
147     my ($self, @voters) = @_;
148     my ($c, $v, $home, @pwentry);
149    
150     for my $v (@voters) {
151     unless (defined ($c = $self->{'voterlist'}->getconf($v))) {
152     die "$v does not correspond to any confirmation number";
153     }
154    
155     @pwentry = getpwnam($v);
156     unless (@pwentry) {
157     print STDERR "Warning: unknown user: $v\n";
158     next;
159     }
160    
161     $home = $pwentry[7];
162     unless (-d $home) {
163     print STDERR "Warning: no directory: $home\n";
164     next;
165     }
166    
167     if (-f "$home/.ballot-$self->{election}-submitted") {
168     my ($b) = Ballot->new($self->{'election'});
169     $b->load("$home/.ballot-$self->{election}-submitted");
170     if ($b->verify) {
171     print STDERR "Errors found in ballot: $v\n";
172     next;
173     }
174     $self->{'full'}{$c} = $b->choices;
175     }
176     elsif (-f "$home/.ballot-$self->{election}") {
177     print STDERR "Warning: $v did not submit their ballot\n";
178     }
179     }
180     }
181    
182     sub write {
183     my ($self, $filename) = @_;
184    
185     $filename ||= $self->{'default_filename'};
186     $self->{'filename'} = $filename;
187    
188     if (-f $filename) {
189     die "$filename already exists; please remove it first";
190     }
191    
192     open(F, ">$filename") or die("can't write to $filename");
193     for my $c (sort keys %{$self->{'full'}}) {
194     printf F "--------- confirmation %04x ---------\n", $c;
195     for my $line (@{$self->{'full'}{$c}}) {
196     print F "@$line\n";
197     }
198     }
199     close F;
200     }
201    
202     ######################################################################
203     # Ballot
204     ######################################################################
205    
206     package Ballot;
207    
208     sub new {
209     my ($class, $election) = @_;
210     my ($self) = {
211     election => $election,
212     filename => '',
213     default_filename => $ENV{'HOME'}."/.ballot-$election",
214     choices => [],
215     };
216    
217     # Bless me, I'm a ballot!
218     bless $self, $class;
219     return $self;
220     }
221    
222     sub load {
223     my ($self, $filename) = @_;
224    
225     $filename ||= $self->{'default_filename'};
226     $self->{'filename'} = $filename;
227    
228     # Load the data file
229     $self->{'choices'} = []; # make sure it's empty
230     open(F, "<$filename") or die("couldn't open $filename");
231     while(<F>) {
232     s/#.*//;
233     next unless /\S/;
234     push @{$self->{'choices'}}, [ split(' ', $_) ];
235     }
236     close(F);
237     die("No data in file") unless @{$self->{'choices'}};
238     }
239    
240     sub populate {
241     my ($self) = @_;
242     $self->load("$Votify::datadir/ballot-$self->{election}");
243     @{$self->{'choices'}} = List::Util::shuffle(@{$self->{'choices'}});
244     }
245    
246     sub choices {
247     my ($self) = @_;
248     $self->{'choices'};
249     }
250    
251     sub write {
252     my ($self, $filename) = @_;
253    
254     if ($Votify::mode ne 'user') {
255     die("we don't write ballots in official mode");
256     }
257    
258     $filename ||= $self->{'default_filename'};
259     $self->{'filename'} = $filename;
260    
261     # Don't ever overwrite a ballot
262     die("File already exists; please remove $filename\n") if -e $filename;
263    
264     # Write the user's ballot
265     open(F, ">$filename") or die "Failed writing $filename";
266     print F <<EOT;
267     # This is a ballot for the $self->{election} election.
268     # Please rank your choices in order; first choice at the top and last choice at
269     # the bottom. You can put choices on the same line to indicate no preference
270     # between them. Any choices you omit from this file are implicitly added at the
271     # end.
272     #
273     # When you're finished editing this, the next step is to verify your ballot
274     # with:
275     #
276     # $Votify::zero --verify $self->{election}
277     #
278     # When that passes and you're satisfied, the final step is to submit your vote:
279     #
280     # $Votify::zero --submit $self->{election}
281     #
282    
283     EOT
284     for (@{$self->{'choices'}}) { print F "@$_\n"; }
285     close(F);
286     }
287    
288     sub verify {
289     my ($self) = @_;
290     my (%h, $master, %mh);
291     my (@dups, @missing, @extra);
292     my ($errors_found);
293    
294     # Load %h from the user's ballot
295     for my $line (@{$self->{'choices'}}) {
296     for my $entry (@$line) {
297     $h{$entry}++;
298     }
299     }
300    
301     # Load the master ballot into another hash and compare them.
302     # The master ballots always do one entry per line, making this a little
303     # easier.
304     $master = Ballot->new($self->{'election'});
305     $master->populate;
306     %mh = map(($_->[0] => 1), @{$master->{'choices'}});
307    
308     # Check for extra entries (write-ins should be supported in the future)
309     for (keys %h) {
310     push @extra, $_ unless exists $mh{$_};
311     }
312    
313     # Check for duplicate entries
314     @dups = grep { $h{$_} > 1 } keys %h;
315    
316     # Check for missing entries (not necessarily an error)
317     for (keys %mh) {
318     push @missing, $_ unless exists $h{$_};
319     }
320    
321     # Report errors and warnings
322     if (@extra) {
323     if ($Votify::mode eq 'user') {
324     print <<EOT;
325     Your ballot has some extra entries that are not part of this election. Sorry,
326     but write-ins are not (yet) supported. Please remove these from your ballot:
327    
328     EOT
329     print map "\t$_\n", @extra;
330     print "\n";
331     }
332     $errors_found++;
333     }
334     if (@dups) {
335     if ($Votify::mode eq 'user') {
336     print <<EOT;
337     Your ballot has some duplicate entries. Please resolve these to a single entry
338     to avoid ambiguities:
339    
340     EOT
341     print map "\t$_\n", @dups;
342     print "\n";
343     }
344     $errors_found++;
345     }
346     if (@{$self->{'choices'}} == 0) {
347     if ($Votify::mode eq 'user') {
348     print <<EOT;
349     Your ballot doesn't contain any entries. You can start over by first removing
350     the existing ballot, then using --new to generate a new ballot. See --help for
351     more information.
352    
353     EOT
354     }
355     $errors_found++;
356     }
357     elsif (@missing and $Votify::mode eq 'user') {
358     print <<EOT;
359     Your ballot is missing some entries. This is not an error, but note that these
360     will be implied as a final line, with no preference between them, like this:
361    
362     EOT
363     print "\t", join(" ", @missing), "\n";
364     print "\n";
365     }
366     if ($Votify::mode eq 'user' and !$errors_found and
367     @{$self->{'choices'}} == 1 and
368     scalar(keys %h) == scalar(keys %mh))
369     {
370     print <<EOT;
371     Your ballot contains all the candidates on a single line! This means you have
372     no preference between the candidates. This is not an error, but note that this
373     is a meaningless ballot that will have no effect on the election.
374    
375     EOT
376     }
377    
378     # Stop if there were errors
379     if ($Votify::mode eq 'user' and $errors_found) {
380     print("There were errors found in your ballot.\n");
381     die("Please correct them and try again.\n\n");
382     }
383     return $errors_found;
384     }
385    
386     1;
387    
388     __END__
389    
390     $Log: votify,v $
391    
392     __END__
393    
394     $Log: votify,v $
395     Revision 1.3 2005/05/09 23:12:02 agriffis
396     Add support for registered voters
397    
398     Revision 1.2 2005/05/05 23:03:46 agriffis
399     Fix indentation (and some output as well)
400    
401     Revision 1.1 2005/05/05 22:05:34 agriffis
402     first pass at Gentoo Foundation voting program
403    
404     # vim:sw=4 noet

  ViewVC Help
Powered by ViewVC 1.1.20