whatif.rb (109 lines of code) (raw):

# STV Explorer using Historical data from ASF Board Votes ##### # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ##### # # Prereqs: # # * svn checkout of foundation:voter and foundation:Meetings # * Web server with the ability to run cgi (Apache httpd recommended) # * Python 2.6.x # * Ruby 1.9.x # * wunderbar gem ([sudo] gem install wunderbar) # * (optional) jQuery http://code.jquery.com/jquery-min.js # # Installation instructions: # # ruby whatif.rb --install=/var/www # # 1) Specify a path that supports cgi, like public-html or Sites. # 2) (optional, but highly recommended) download jquery-min.js into # your installation directory. # # Execution instructions: # # Point your web browser at your cgi script. For best results, use # Firefox 4 or a WebKit based browser, like Google Chrome. MEETINGS = File.expand_path('../Meetings') unless defined? MEETINGS WHATIF = './whatif.py' unless defined? WHATIF require 'wunderbar' require 'tempfile' def raw_votes(date) all_votes = Dir["#{MEETINGS}/*/raw_board_votes.txt"] if date result = "#{MEETINGS}/#{date}/raw_board_votes.txt" else result = all_votes.sort.last end result end def ini(vote) vote.sub('/raw_','/').sub('votes.','nominations.').sub('.txt','.ini') end def filtered_election(votes, seats, candidates) list = candidates.join(' ') output = `#{WHATIF} #{votes} #{seats} #{list}` output.scan(/.*elected$/).inject(Hash.new('none')) do |results, line| name, status = line.scan(/^(.*?)\s+(n?o?t?\s?elected)$/).flatten results.merge({name.gsub(/[^[[:alnum:]]]/,'') => status.gsub(/\s/, '-')}) end end # XMLHttpRequest (AJAX) _json do nominees = File.read(ini(raw_votes(@date))).scan(/^\w:\s*(.*)/).flatten candidates = params.keys & nominees.map {|name| name.gsub(/[^[[:alnum:]]]/,'')} _! filtered_election(raw_votes(@date), @seats, candidates) end # main output _html do _head_ do _title 'STV Explorer' _style! %{ h1 {font-family: sans-serif; font-weight: normal} select {display: block; margin: 0 0 1em 1em; font-size: 140%} label div {display: inline-block; min-width: 12em; font-size: x-large} label div {-webkit-transition: background-color 1s} label div {-moz-transition: background-color 1s} label {float: left; clear: both} label[for=seats] {display: inline; line-height: 500%} p, input[type=checkbox] {margin-left: 1em} p, input[type=submit] {display: block; clear: both} .elected {background: #0F0} .not-elected {background: #F00} .none {background: yellow} } _script src: 'assets/jquery-min.js' end _body? do _h1_ 'STV Explorer' nominees = Hash[File.read(ini(raw_votes(@date))).scan(/^\w:\s*(.*)/). flatten.map {|name| [name.gsub(/[^[[:alnum:]]]/,''), name]}] candidates = params.keys & nominees.keys candidates = nominees.keys if candidates.empty? or @reset @seats ||= '9' results = filtered_election(raw_votes(@date), @seats, candidates) # form of nominees and seats _form method: 'post', id: 'vote' do _select name: 'date' do Dir["#{MEETINGS}/*/raw_board_votes.txt"].sort.reverse.each do |votes| next unless File.exist? ini(votes) date = votes[/(\d+)\/raw_board_votes.txt$/,1] display = date.sub(/(\d{4})(\d\d)(\d\d)/,'\1-\2-\3') _option display, value: date, selected: (votes == raw_votes(@date)) end end nominees.sort.each do |id, name| _label_ id: id do _input type: 'checkbox', name: id, checked: candidates.include?(id) _div name, class: results[id] end end _label_ for: 'seats' do _span 'seats:' _input name: 'seats', id: 'seats', value: @seats, size: 2, type: 'number', min: 1, max: nominees.length-1 end _input type: 'submit', value: 'submit', name: 'submit' end _p_ do _a "Member's Meeting Information", href: 'https://whimsy.apache.org/members/meeting' end _script %{ // submit form using XHR; update class for labels based on results function refresh() { $.post('', $('#vote').serialize(), function(results) { for (var name in results) { $('#'+name+' div').attr('class', results[name]); } }, 'json'); return false; } // On checkbox click, remove class from associated label & refresh $(':checkbox').click(function() { $('div', $(this).parent()).attr('class', 'none'); refresh(); }); // reset whenever the date changes $('select').change(function() { $('input[value=submit]').attr('name', 'reset'); $('input[value=submit]').click(); }); // If JS is enabled, we don't need a submit button $('input[type=submit]').hide(); // Refresh on change in number of seats $('#seats').on('input', function() {return refresh()}); } end end __END__ MEETINGS = '../Meetings'