| @@ -13,13 +13,6 @@ OUTPUT_DIR = '_site' | |||||
| VIEWS_DIR = File.join('..', 'views') | VIEWS_DIR = File.join('..', 'views') | ||||
| LAYOUT_FN = File.join(VIEWS_DIR, 'layout.haml') | LAYOUT_FN = File.join(VIEWS_DIR, 'layout.haml') | ||||
| @log = Logger.new($stdout) | |||||
| @log.level = Logger::INFO | |||||
| @log.info "Build starts." | |||||
| @log.info "Output directory is: #{OUTPUT_DIR}" | |||||
| @pages = 0 | |||||
| def write_page(path_items, template, locals = {}) | def write_page(path_items, template, locals = {}) | ||||
| dir = File.join(path_items) | dir = File.join(path_items) | ||||
| FileUtils.mkdir_p(dir) | FileUtils.mkdir_p(dir) | ||||
| @@ -38,211 +31,237 @@ def write_page(path_items, template, locals = {}) | |||||
| # https://support.google.com/webmasters/answer/183668?hl=en&ref_topic=4581190 | # https://support.google.com/webmasters/answer/183668?hl=en&ref_topic=4581190 | ||||
| end | end | ||||
| working_dir = File.join(Dir.pwd, OUTPUT_DIR) | |||||
| # Recursively delete working directory to ensure no redundant files are left behind from previous builds. | |||||
| FileUtils.rm_rf(working_dir) | |||||
| Dir.mkdir(working_dir) unless File.directory?(working_dir) | |||||
| Dir.chdir(working_dir) | |||||
| # Copy `public` dir to output dir | |||||
| FileUtils.copy_entry(File.join('..', 'public'), '.') | |||||
| # Home page | |||||
| locals = { | |||||
| future_elections: Election.future, | |||||
| past_elections: Election.past | |||||
| } | |||||
| write_page('.', 'index', locals) | |||||
| # Election pages | |||||
| Election.each do |e| | |||||
| locals = { | |||||
| body: Body.first(:slug => e.body.slug), | |||||
| election: Election.first(:body => e.body, :d => e.d), | |||||
| elections_for_this_body: Election.all(:body => e.body, :order => [:d]), | |||||
| total_seats: Candidacy.sum(:seats, :election => e), | |||||
| total_votes: Candidacy.sum(:votes, :election => e) | |||||
| } | |||||
| # There's got to be a better way to do this, either with SQL or Datamapper | |||||
| locals['total_districts'] = repository(:default).adapter.select(" | |||||
| SELECT district_id | |||||
| FROM candidacies | |||||
| WHERE election_id = ? | |||||
| GROUP BY district_id | |||||
| ORDER BY district_id | |||||
| ", e.id).count | |||||
| locals['results_by_party'] = repository(:default).adapter.select(" | |||||
| SELECT | |||||
| p.colour, | |||||
| p.name, | |||||
| SUM(c.votes) AS votez, | |||||
| SUM(c.seats) AS seatz, | |||||
| COUNT(*) AS cands | |||||
| FROM candidacies c | |||||
| LEFT JOIN parties p ON p.id = c.party_id | |||||
| WHERE c.election_id = ? | |||||
| GROUP BY c.party_id, p.colour, p.name | |||||
| def gen_info_pages | |||||
| write_page('about', 'about') | |||||
| write_page('guides', 'guides') | |||||
| write_page(%w(guides how-the-parliament-election-works), 'parliament') | |||||
| write_page(%w(guides how-the-council-election-works), 'election') | |||||
| end | |||||
| ORDER BY seatz DESC, votez DESC | |||||
| ", e.id) | |||||
| def gen_bodies_pages | |||||
| # Bodies index | |||||
| dir = 'bodies' | |||||
| FileUtils.mkdir_p(dir) | |||||
| @log.debug dir | |||||
| fn = File.join(dir, 'index.html') | |||||
| FileUtils.touch(fn) # empty file | |||||
| @log.info fn | |||||
| write_page(['bodies', e.body.slug, 'elections', e.d.to_s], 'electionsummary', locals) | |||||
| # Body detail pages | |||||
| Body.each do |b| | |||||
| locals = { | |||||
| body: b, | |||||
| districts: District.all(:body => b, :order => [:name]) | |||||
| } | |||||
| locals['elections'] = repository(:default).adapter.select(" | |||||
| SELECT | |||||
| e.id, | |||||
| e.kind, | |||||
| e.d, | |||||
| SUM(p.ballot_papers_issued)::float / SUM(p.electorate) * 100 AS turnout_percent | |||||
| FROM elections e | |||||
| LEFT JOIN polls p | |||||
| ON e.id = p.election_id | |||||
| WHERE e.body_id = ? | |||||
| GROUP BY p.election_id, e.id | |||||
| ORDER BY e.d DESC | |||||
| ", b.id) | |||||
| write_page(['bodies', b.slug], 'body', locals) | |||||
| # Districts for this body | |||||
| b.districts.each do |d| | |||||
| locals = { | |||||
| district: d, | |||||
| body: b | |||||
| } | |||||
| write_page(['bodies', b.slug, b.districts_name, d.slug], 'district', locals) | |||||
| end | |||||
| end | |||||
| end | |||||
| def gen_candidates_pages | |||||
| # Candidate index | |||||
| locals = { candidates: Candidate.all(:order => [ :surname, :forenames ]) } | |||||
| write_page('candidates', 'candidates', locals) | |||||
| # District results for this election (resultsdistrict) | |||||
| # Loop through all districts in this election | |||||
| e.candidacies.districts.each do |d| | |||||
| total_seats = Candidacy.sum(:seats, :district => d, :election => e) | |||||
| total_votes = Candidacy.sum(:votes, :district => d, :election => e) | |||||
| poll = Poll.get(d.id, e.id) | |||||
| # Candidate pages | |||||
| # FIXME: What do we do about deleted candidates/redirects? | |||||
| Candidate.each do |c| | |||||
| locals = { | locals = { | ||||
| district: d, | |||||
| body: d.body, | |||||
| election: e, | |||||
| candidacies: Candidacy.all(:district => d, :election => e, :order => [:position]), | |||||
| total_votes: total_votes, | |||||
| total_candidates: Candidacy.count(:district => d, :election => e), | |||||
| total_seats: total_seats, | |||||
| districts_in_this_election: e.candidacies.districts, | |||||
| poll: poll | |||||
| candidate: c | |||||
| } | } | ||||
| locals['share_message'] = nil | |||||
| if total_seats == 1 | |||||
| locals['share_denominator'] = total_votes | |||||
| elsif poll && poll.valid_ballot_papers | |||||
| locals['share_denominator'] = poll.valid_ballot_papers | |||||
| else | |||||
| locals['share_denominator'] = total_votes / total_seats | |||||
| locals['share_message'] = "The vote share percentages have been estimated as we don't have data for the number of valid ballot papers in this poll." | |||||
| end | |||||
| # Postgres: All the columns selected when using GROUP BY must either be aggregate functions or appear in the GROUP BY clause | |||||
| locals['results_by_party'] = repository(:default).adapter.select(" | |||||
| locals['candidacies'] = repository(:default).adapter.select(" | |||||
| SELECT | SELECT | ||||
| e.d, | |||||
| c.*, | |||||
| p.name AS party_name, | p.name AS party_name, | ||||
| p.colour AS party_colour, | p.colour AS party_colour, | ||||
| COUNT(c.id) AS num_candidates, | |||||
| SUM(c.seats) AS num_seats, | |||||
| SUM(c.votes) AS total_votes | |||||
| b.name AS body_name, | |||||
| b.slug AS body_slug, | |||||
| b.districts_name AS districts_name, | |||||
| d.name AS district_name, | |||||
| d.slug AS district_slug | |||||
| FROM candidacies c | FROM candidacies c | ||||
| LEFT JOIN parties p | |||||
| INNER JOIN elections e | |||||
| ON c.election_id = e.id | |||||
| INNER JOIN parties p | |||||
| ON c.party_id = p.id | ON c.party_id = p.id | ||||
| WHERE c.district_id = ? | |||||
| AND c.election_id = ? | |||||
| INNER JOIN bodies b | |||||
| ON e.body_id = b.id | |||||
| INNER JOIN districts d | |||||
| ON c.district_id = d.id | |||||
| GROUP BY p.name, p.colour | |||||
| WHERE c.candidate_id = ? | |||||
| ORDER BY total_votes DESC | |||||
| ", d.id, e.id) | |||||
| ORDER BY d | |||||
| ", c.id) | |||||
| write_page(['bodies', e.body.slug, 'elections', e.d.to_s, e.body.districts_name, d.slug], 'resultsdistrict', locals) | |||||
| write_page(['candidates', c.id.to_s], 'candidate', locals) | |||||
| end | end | ||||
| end | end | ||||
| # Candidate index | |||||
| locals = { candidates: Candidate.all(:order => [ :surname, :forenames ]) } | |||||
| write_page('candidates', 'candidates', locals) | |||||
| # Candidate pages | |||||
| # FIXME: What do we do about deleted candidates/redirects? | |||||
| Candidate.each do |c| | |||||
| locals = { | |||||
| candidate: c | |||||
| } | |||||
| locals['candidacies'] = repository(:default).adapter.select(" | |||||
| SELECT | |||||
| e.d, | |||||
| c.*, | |||||
| p.name AS party_name, | |||||
| p.colour AS party_colour, | |||||
| b.name AS body_name, | |||||
| b.slug AS body_slug, | |||||
| b.districts_name AS districts_name, | |||||
| d.name AS district_name, | |||||
| d.slug AS district_slug | |||||
| FROM candidacies c | |||||
| INNER JOIN elections e | |||||
| ON c.election_id = e.id | |||||
| INNER JOIN parties p | |||||
| ON c.party_id = p.id | |||||
| INNER JOIN bodies b | |||||
| ON e.body_id = b.id | |||||
| def gen_elections_pages | |||||
| # Election pages | |||||
| Election.each do |e| | |||||
| locals = { | |||||
| body: Body.first(:slug => e.body.slug), | |||||
| election: Election.first(:body => e.body, :d => e.d), | |||||
| elections_for_this_body: Election.all(:body => e.body, :order => [:d]), | |||||
| total_seats: Candidacy.sum(:seats, :election => e), | |||||
| total_votes: Candidacy.sum(:votes, :election => e) | |||||
| } | |||||
| INNER JOIN districts d | |||||
| ON c.district_id = d.id | |||||
| # There's got to be a better way to do this, either with SQL or Datamapper | |||||
| locals['total_districts'] = repository(:default).adapter.select(" | |||||
| SELECT district_id | |||||
| FROM candidacies | |||||
| WHERE election_id = ? | |||||
| GROUP BY district_id | |||||
| ORDER BY district_id | |||||
| ", e.id).count | |||||
| WHERE c.candidate_id = ? | |||||
| locals['results_by_party'] = repository(:default).adapter.select(" | |||||
| SELECT | |||||
| p.colour, | |||||
| p.name, | |||||
| SUM(c.votes) AS votez, | |||||
| SUM(c.seats) AS seatz, | |||||
| COUNT(*) AS cands | |||||
| ORDER BY d | |||||
| ", c.id) | |||||
| FROM candidacies c | |||||
| write_page(['candidates', c.id.to_s], 'candidate', locals) | |||||
| LEFT JOIN parties p ON p.id = c.party_id | |||||
| WHERE c.election_id = ? | |||||
| GROUP BY c.party_id, p.colour, p.name | |||||
| ORDER BY seatz DESC, votez DESC | |||||
| ", e.id) | |||||
| write_page(['bodies', e.body.slug, 'elections', e.d.to_s], 'electionsummary', locals) | |||||
| # District results for this election (resultsdistrict) | |||||
| # Loop through all districts in this election | |||||
| e.candidacies.districts.each do |d| | |||||
| total_seats = Candidacy.sum(:seats, :district => d, :election => e) | |||||
| total_votes = Candidacy.sum(:votes, :district => d, :election => e) | |||||
| poll = Poll.get(d.id, e.id) | |||||
| locals = { | |||||
| district: d, | |||||
| body: d.body, | |||||
| election: e, | |||||
| candidacies: Candidacy.all(:district => d, :election => e, :order => [:position]), | |||||
| total_votes: total_votes, | |||||
| total_candidates: Candidacy.count(:district => d, :election => e), | |||||
| total_seats: total_seats, | |||||
| districts_in_this_election: e.candidacies.districts, | |||||
| poll: poll | |||||
| } | |||||
| locals['share_message'] = nil | |||||
| if total_seats == 1 | |||||
| locals['share_denominator'] = total_votes | |||||
| elsif poll && poll.valid_ballot_papers | |||||
| locals['share_denominator'] = poll.valid_ballot_papers | |||||
| else | |||||
| locals['share_denominator'] = total_votes / total_seats | |||||
| locals['share_message'] = "The vote share percentages have been estimated as we don't have data for the number of valid ballot papers in this poll." | |||||
| end | |||||
| # Postgres: All the columns selected when using GROUP BY must either be aggregate functions or appear in the GROUP BY clause | |||||
| locals['results_by_party'] = repository(:default).adapter.select(" | |||||
| SELECT | |||||
| p.name AS party_name, | |||||
| p.colour AS party_colour, | |||||
| COUNT(c.id) AS num_candidates, | |||||
| SUM(c.seats) AS num_seats, | |||||
| SUM(c.votes) AS total_votes | |||||
| FROM candidacies c | |||||
| LEFT JOIN parties p | |||||
| ON c.party_id = p.id | |||||
| WHERE c.district_id = ? | |||||
| AND c.election_id = ? | |||||
| GROUP BY p.name, p.colour | |||||
| ORDER BY total_votes DESC | |||||
| ", d.id, e.id) | |||||
| write_page(['bodies', e.body.slug, 'elections', e.d.to_s, e.body.districts_name, d.slug], 'resultsdistrict', locals) | |||||
| end | |||||
| end | |||||
| end | end | ||||
| # Bodies index | |||||
| dir = 'bodies' | |||||
| FileUtils.mkdir_p(dir) | |||||
| @log.debug dir | |||||
| fn = File.join(dir, 'index.html') | |||||
| FileUtils.touch(fn) # empty file | |||||
| @log.info fn | |||||
| # Body detail pages | |||||
| Body.each do |b| | |||||
| def gen_homepage | |||||
| locals = { | locals = { | ||||
| body: b, | |||||
| districts: District.all(:body => b, :order => [:name]) | |||||
| future_elections: Election.future, | |||||
| past_elections: Election.past | |||||
| } | } | ||||
| locals['elections'] = repository(:default).adapter.select(" | |||||
| SELECT | |||||
| e.id, | |||||
| e.kind, | |||||
| e.d, | |||||
| SUM(p.ballot_papers_issued)::float / SUM(p.electorate) * 100 AS turnout_percent | |||||
| FROM elections e | |||||
| LEFT JOIN polls p | |||||
| ON e.id = p.election_id | |||||
| write_page('.', 'index', locals) | |||||
| end | |||||
| WHERE e.body_id = ? | |||||
| def create_output_dir | |||||
| working_dir = File.join(Dir.pwd, OUTPUT_DIR) | |||||
| # Recursively delete working directory to ensure no redundant files are left behind from previous builds. | |||||
| FileUtils.rm_rf(working_dir) | |||||
| Dir.mkdir(working_dir) unless File.directory?(working_dir) | |||||
| Dir.chdir(working_dir) | |||||
| # Copy `public` dir to output dir | |||||
| FileUtils.copy_entry(File.join('..', 'public'), '.') | |||||
| end | |||||
| GROUP BY p.election_id, e.id | |||||
| ORDER BY e.d DESC | |||||
| ", b.id) | |||||
| @log = Logger.new($stdout) | |||||
| @log.level = Logger::INFO | |||||
| @log.info "Build starts." | |||||
| @log.info "Output directory is: #{OUTPUT_DIR}" | |||||
| write_page(['bodies', b.slug], 'body', locals) | |||||
| @pages = 0 # count the number of pages generated | |||||
| # Districts for this body | |||||
| b.districts.each do |d| | |||||
| locals = { | |||||
| district: d, | |||||
| body: b | |||||
| } | |||||
| write_page(['bodies', b.slug, b.districts_name, d.slug], 'district', locals) | |||||
| end | |||||
| end | |||||
| create_output_dir | |||||
| write_page('about', 'about') | |||||
| write_page('guides', 'guides') | |||||
| write_page(%w(guides how-the-parliament-election-works), 'parliament') | |||||
| write_page(%w(guides how-the-council-election-works), 'election') | |||||
| gen_homepage | |||||
| gen_elections_pages | |||||
| gen_bodies_pages | |||||
| gen_info_pages | |||||
| gen_candidates_pages | |||||
| @log.info "Build complete. %d pages generated in %0.2f seconds." % [ @pages, Process.clock_gettime(Process::CLOCK_MONOTONIC) - t_start ] | @log.info "Build complete. %d pages generated in %0.2f seconds." % [ @pages, Process.clock_gettime(Process::CLOCK_MONOTONIC) - t_start ] | ||||