|
@@ -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 ] |