aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunasSudo <runassudo@yingtongli.me>2021-10-28 01:23:33 +1100
committerRunasSudo <runassudo@yingtongli.me>2021-10-28 01:23:55 +1100
commit116d4c385c6f9ba270876bca5fe90c1cb8a01f31 (patch)
tree4cdde56b4ca992d99f1ea286c2e66ef679eea738
parent63a649405b9d50b2cfdbdad8d5cf4edb81638ce2 (diff)
Build docs as HTML
-rw-r--r--.gitignore7
-rw-r--r--README.md2
-rw-r--r--docs/options.md8
-rw-r--r--homepage/404.html25
-rw-r--r--homepage/Gemfile15
-rw-r--r--homepage/Gemfile.lock72
-rw-r--r--homepage/_config.yml28
-rw-r--r--homepage/_layouts/default.html48
-rw-r--r--homepage/_layouts/post.html11
-rw-r--r--homepage/_plugins/include_absolute.rb168
-rw-r--r--homepage/about.md12
-rw-r--r--homepage/assets/headerimg.pngbin0 -> 94088 bytes
-rw-r--r--homepage/docs/options.md23
-rw-r--r--homepage/index.html111
14 files changed, 525 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index 42680c5..9599041 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,10 @@
/target
/html/opentally.js
/html/opentally_*.wasm
+
+# Jekyll
+/homepage/_site
+/homepage/.sass-cache
+/homepage/.jekyll-cache
+/homepage/.jekyll-metadata
+/homepage/vendor
diff --git a/README.md b/README.md
index 0d72cb7..09d7ee2 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ OpenTally is highly customisable, including options for:
After preparing the [BLT file](https://yingtongli.me/git/OpenTally/about/docs/blt.md), open the web UI. Select the BLT file, and click *Count*. OpenTally will count the election and display the results in a count sheet.
-By clicking *Show advanced options*, you can customise the options used for the count. A detailed explanation of the various options can be found [here](https://yingtongli.me/git/OpenTally/about/docs/options.md).
+By clicking *Show advanced options*, you can customise the options used for the count. A detailed explanation of the various options can be found [here](/opentally/docs/options.html).
Once the count is complete, you can click *Print result* to generate a printable result report.
diff --git a/docs/options.md b/docs/options.md
index 59d7b87..e974f9b 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -27,7 +27,7 @@ The preset dropdown allows you to choose from a hardcoded list of preloaded STV
Exceptions:
-* [E1] When generating random numbers, OpenTally uses a [deterministic random number generator based on SHA-256](rng.md), rather than the Wichmann–Hill(-based) algorithm.
+* [E1] When generating random numbers, OpenTally uses a [deterministic random number generator based on SHA-256](https://yingtongli.me/git/OpenTally/about/docs/rng.md), rather than the Wichmann–Hill(-based) algorithm.
* [E2] When breaking ties backwards, OpenTally selects the candidate who had more/fewer votes at the last stage when *any* tied candidate had more/fewer votes, rather than the method described in the legislation (when each all had unequal votes). The OpenTally developers regard the method described in the legislation as a defect. For an independent discussion, see <a href="https://dl.acm.org/doi/10.1145/3014812.3014837">Conway et al.</a>
* [E3] A tie between 2 candidates for the final vacancy will be broken backwards then at random, rather than the method described in the legislation.
* [E4] Bulk exclusion is not performed, as the prescribed rules are more conservative than OpenTally's. See also the section on *Bulk exclusion* for further discussion.
@@ -36,7 +36,7 @@ Exceptions:
* [E7] No distinction is made between stages and substages (during exclusion). This affects only the numbering of stages and not the result.
* [E8] By default, the quota is always calculated to 2 decimal places. For full ERS76 (ERS73) compliance, set *Round quota to 0 d.p.* when the quota is more than 100 (100 or more).
-For details of validation, see [validation.md](validation.md).
+For details of validation, see [validation.md](https://yingtongli.me/git/OpenTally/about/docs/validation.md).
This functionality is not available on the command line.
@@ -158,11 +158,11 @@ This option allows you to input an arbitrary value to seed the deterministic ran
The default value is the current date, formatted YYYYMMDD.
-The algorithm used by the random number generator is specified at [rng.md](rng.md).
+The algorithm used by the random number generator is specified at [rng.md](https://yingtongli.me/git/OpenTally/about/docs/rng.md).
## Constraints (--constraints)
-This file selector allows you to load a [CON file](con.md) specifying constraints on the election. For example, if a certain minimum or maximum number of candidates can be elected from a particular category.
+This file selector allows you to load a [CON file](https://yingtongli.me/git/OpenTally/about/docs/con.md) specifying constraints on the election. For example, if a certain minimum or maximum number of candidates can be elected from a particular category.
OpenTally applies constraints using the Grey–Fitzgerald method. Whenever a candidate is declared elected or excluded, any candidate who must be elected to secure a conformant result is deemed *guarded*, and any candidate who must not be elected to secure a conformant result is deemed *doomed*. Any candidate who is doomed is excluded at the next opportunity. Any candidate who is guarded is prevented from being excluded.
diff --git a/homepage/404.html b/homepage/404.html
new file mode 100644
index 0000000..086a5c9
--- /dev/null
+++ b/homepage/404.html
@@ -0,0 +1,25 @@
+---
+permalink: /404.html
+layout: default
+---
+
+<style type="text/css" media="screen">
+ .container {
+ margin: 10px auto;
+ max-width: 600px;
+ text-align: center;
+ }
+ h1 {
+ margin: 30px 0;
+ font-size: 4em;
+ line-height: 1;
+ letter-spacing: -1px;
+ }
+</style>
+
+<div class="container">
+ <h1>404</h1>
+
+ <p><strong>Page not found :(</strong></p>
+ <p>The requested page could not be found.</p>
+</div>
diff --git a/homepage/Gemfile b/homepage/Gemfile
new file mode 100644
index 0000000..b642850
--- /dev/null
+++ b/homepage/Gemfile
@@ -0,0 +1,15 @@
+source "https://rubygems.org"
+gem "jekyll", "~> 4.2.0"
+
+# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
+# and associated library.
+platforms :mingw, :x64_mingw, :mswin, :jruby do
+ gem "tzinfo", "~> 1.2"
+ gem "tzinfo-data"
+end
+
+# Performance-booster for watching directories on Windows
+gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
+
+# For Ruby 3.0
+gem "webrick", "~> 1.7"
diff --git a/homepage/Gemfile.lock b/homepage/Gemfile.lock
new file mode 100644
index 0000000..91a5227
--- /dev/null
+++ b/homepage/Gemfile.lock
@@ -0,0 +1,72 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.8.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ colorator (1.1.0)
+ concurrent-ruby (1.1.9)
+ em-websocket (0.5.2)
+ eventmachine (>= 0.12.9)
+ http_parser.rb (~> 0.6.0)
+ eventmachine (1.2.7)
+ ffi (1.15.4)
+ forwardable-extended (2.6.0)
+ http_parser.rb (0.6.0)
+ i18n (1.8.10)
+ concurrent-ruby (~> 1.0)
+ jekyll (4.2.1)
+ addressable (~> 2.4)
+ colorator (~> 1.0)
+ em-websocket (~> 0.5)
+ i18n (~> 1.0)
+ jekyll-sass-converter (~> 2.0)
+ jekyll-watch (~> 2.0)
+ kramdown (~> 2.3)
+ kramdown-parser-gfm (~> 1.0)
+ liquid (~> 4.0)
+ mercenary (~> 0.4.0)
+ pathutil (~> 0.9)
+ rouge (~> 3.0)
+ safe_yaml (~> 1.0)
+ terminal-table (~> 2.0)
+ jekyll-sass-converter (2.1.0)
+ sassc (> 2.0.1, < 3.0)
+ jekyll-watch (2.2.1)
+ listen (~> 3.0)
+ kramdown (2.3.1)
+ rexml
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
+ liquid (4.0.3)
+ listen (3.7.0)
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
+ mercenary (0.4.0)
+ pathutil (0.16.2)
+ forwardable-extended (~> 2.6)
+ public_suffix (4.0.6)
+ rb-fsevent (0.11.0)
+ rb-inotify (0.10.1)
+ ffi (~> 1.0)
+ rexml (3.2.5)
+ rouge (3.26.1)
+ safe_yaml (1.0.5)
+ sassc (2.4.0)
+ ffi (~> 1.9)
+ terminal-table (2.0.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ unicode-display_width (1.8.0)
+ webrick (1.7.0)
+
+PLATFORMS
+ x86_64-linux
+
+DEPENDENCIES
+ jekyll (~> 4.2.0)
+ tzinfo (~> 1.2)
+ tzinfo-data
+ wdm (~> 0.1.1)
+ webrick (~> 1.7)
+
+BUNDLED WITH
+ 2.2.26
diff --git a/homepage/_config.yml b/homepage/_config.yml
new file mode 100644
index 0000000..c53c7cf
--- /dev/null
+++ b/homepage/_config.yml
@@ -0,0 +1,28 @@
+# Site settings
+
+title: OpenTally
+baseurl: "/opentally"
+url: "https://yingtongli.me"
+
+git_url: "https://yingtongli.me/git/OpenTally"
+
+# Build settings
+
+plugins: []
+kramdown:
+ smart_quotes: ["apos", "apos", "quot", "quot"]
+ typographic_symbols: {"mdash": "---", "ndash": "--"}
+
+# Exclude from processing.
+
+# exclude:
+# - .sass-cache/
+# - .jekyll-cache/
+# - gemfiles/
+# - Gemfile
+# - Gemfile.lock
+# - node_modules/
+# - vendor/bundle/
+# - vendor/cache/
+# - vendor/gems/
+# - vendor/ruby/
diff --git a/homepage/_layouts/default.html b/homepage/_layouts/default.html
new file mode 100644
index 0000000..1b10045
--- /dev/null
+++ b/homepage/_layouts/default.html
@@ -0,0 +1,48 @@
+---
+---
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
+ <title>{% if post.title %}{{ post.title }}{% else %}{{ site.title }}{% endif %}</title>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css" integrity="sha256-PDJQdTN7dolQWDASIoBVrjkuOEaI137FI15sqI3Oxu8=" crossorigin="anonymous">
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
+ </head>
+ <body class="d-flex flex-column h-100">
+ <main class="flex-shrink-0">
+ <!-- Navigation-->
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
+ <div class="container px-5">
+ <a class="navbar-brand" href="{{ site.baseurl }}/">{{ site.title }}</a>
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
+ <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
+ <li class="nav-item"><a class="nav-link" href="{{ site.baseurl }}/">Home</a></li>
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle" id="navbarDropdownBlog" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Documentation</a>
+ <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownBlog">
+ <li><a class="dropdown-item" href="{{ site.baseurl }}/about.html">About OpenTally</a></li>
+ <li><a class="dropdown-item" href="{{ site.baseurl }}/docs/options.html">Advanced Options</a></li>
+ </ul>
+ </li>
+ <li class="nav-item"><a class="nav-link" href="{{ site.git_url }}/tree/">Source Code</a></li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+ {{ content }}
+ </main>
+ <!-- Footer-->
+ <footer class="bg-dark py-4 mt-auto">
+ <div class="container px-5">
+ <div class="row align-items-center justify-content-between flex-column flex-sm-row">
+ <div class="col-auto"><div class="small m-0 text-white">Copyright &copy; <a href="{{ site.url }}" style="color:inherit;">Lee Yingtong Li</a> (RunasSudo) 2021</div></div>
+ </div>
+ </div>
+ </footer>
+ <!-- Bootstrap core JS-->
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha256-XDbijJp72GS2c+Ij234ZNJIyJ1Nv+9+HH1i28JuayMk=" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/homepage/_layouts/post.html b/homepage/_layouts/post.html
new file mode 100644
index 0000000..9dda375
--- /dev/null
+++ b/homepage/_layouts/post.html
@@ -0,0 +1,11 @@
+---
+layout: default
+---
+
+<section class="py-5">
+ <div class="container px-5">
+ <h1 class="mb-4">{{ page.title }}</h1>
+
+ {{ content }}
+ </div>
+</section>
diff --git a/homepage/_plugins/include_absolute.rb b/homepage/_plugins/include_absolute.rb
new file mode 100644
index 0000000..7112911
--- /dev/null
+++ b/homepage/_plugins/include_absolute.rb
@@ -0,0 +1,168 @@
+module Jekyll
+ module Tags
+ class IncludeAbsoluteTagError < StandardError
+ attr_accessor :path
+
+ def initialize(msg, path)
+ super(msg)
+ @path = path
+ end
+ end
+
+ class IncludeAbsoluteTag < Liquid::Tag
+ VALID_SYNTAX = %r!
+ ([\w-]+)\s*=\s*
+ (?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))
+ !x
+ VARIABLE_SYNTAX = %r!
+ (?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)
+ (?<params>.*)
+ !mx
+
+ FULL_VALID_SYNTAX = %r!\A\s*(?:#{VALID_SYNTAX}(?=\s|\z)\s*)*\z!
+ VALID_FILENAME_CHARS = %r!^[\w/\.-]+$!
+
+ def initialize(tag_name, markup, tokens)
+ super
+ matched = markup.strip.match(VARIABLE_SYNTAX)
+ if matched
+ @file = matched["variable"].strip
+ @params = matched["params"].strip
+ else
+ @file, @params = markup.strip.split(%r!\s+!, 2)
+ end
+ validate_params if @params
+ @tag_name = tag_name
+ end
+
+ def syntax_example
+ "{% #{@tag_name} 'file.ext' param='value' param2='value' %}"
+ end
+
+ def parse_params(context)
+ params = {}
+ markup = @params
+
+ while (match = VALID_SYNTAX.match(markup))
+ markup = markup[match.end(0)..-1]
+
+ value = if match[2]
+ match[2].gsub(%r!\\"!, '"')
+ elsif match[3]
+ match[3].gsub(%r!\\'!, "'")
+ elsif match[4]
+ context[match[4]]
+ end
+
+ params[match[1]] = value
+ end
+ params
+ end
+
+ def validate_file_name(file)
+ if file !~ VALID_FILENAME_CHARS
+ raise ArgumentError, <<-MSG
+Invalid syntax for include tag. File contains invalid characters or sequences:
+
+ #{file}
+
+Valid syntax:
+
+ #{syntax_example}
+
+MSG
+ end
+ end
+
+ def validate_params
+ unless @params =~ FULL_VALID_SYNTAX
+ raise ArgumentError, <<-MSG
+Invalid syntax for include tag:
+
+ #{@params}
+
+Valid syntax:
+
+ #{syntax_example}
+
+MSG
+ end
+ end
+
+ # Grab file read opts in the context
+ def file_read_opts(context)
+ context.registers[:site].file_read_opts
+ end
+
+ # Render the variable if required
+ def render_variable(context)
+ if @file =~ VARIABLE_SYNTAX
+ partial = context.registers[:site]
+ .liquid_renderer
+ .file("(variable)")
+ .parse(@file)
+ partial.render!(context)
+ end
+ end
+
+ def render(context)
+ site = context.registers[:site]
+
+ file = render_variable(context) || @file
+ # strip leading and trailing quote's
+ file = file.gsub!(/\A'|'\Z/, '')
+ validate_file_name(file)
+
+ source = File.expand_path(context.registers[:site].config['source']).freeze
+ path = File.join(source, file)
+ return unless path
+
+ partial = Liquid::Template.parse(read_file(path, context))
+
+ context.stack do
+ context["include"] = parse_params(context) if @params
+ begin
+ partial.render!(context)
+ rescue Liquid::Error => e
+ e.template_name = path
+ e.markup_context = "included " if e.markup_context.nil?
+ raise e
+ end
+ end
+ end
+
+ def valid_include_file?(path, dir, safe)
+ !outside_site_source?(path, dir, safe) && File.file?(path)
+ end
+
+ def outside_site_source?(path, dir, safe)
+ safe && !realpath_prefixed_with?(path, dir)
+ end
+
+ def realpath_prefixed_with?(path, dir)
+ File.exist?(path) && File.realpath(path).start_with?(dir)
+ rescue StandardError
+ false
+ end
+
+ # This method allows to modify the file content by inheriting from the class.
+ def read_file(file, context)
+ File.read(file, **file_read_opts(context))
+ end
+
+ private
+
+ def could_not_locate_message(file, includes_dirs, safe)
+ message = "Could not locate the included file '#{file}' in any of "\
+ "#{includes_dirs}. Ensure it exists in one of those directories and"
+ message + if safe
+ " is not a symlink as those are not allowed in safe mode."
+ else
+ ", if it is a symlink, does not point outside your site source."
+ end
+ end
+ end
+ end
+end
+
+Liquid::Template.register_tag("include_absolute", Jekyll::Tags::IncludeAbsoluteTag)
diff --git a/homepage/about.md b/homepage/about.md
new file mode 100644
index 0000000..648190f
--- /dev/null
+++ b/homepage/about.md
@@ -0,0 +1,12 @@
+---
+layout: post
+title: "About OpenTally"
+---
+
+<div class="md-content" markdown="1">{% include_absolute '../README.md' %}</div>
+
+<style type="text/css">
+ .md-content h1 {
+ display: none;
+ }
+</style>
diff --git a/homepage/assets/headerimg.png b/homepage/assets/headerimg.png
new file mode 100644
index 0000000..0aa52cf
--- /dev/null
+++ b/homepage/assets/headerimg.png
Binary files differ
diff --git a/homepage/docs/options.md b/homepage/docs/options.md
new file mode 100644
index 0000000..569e1f0
--- /dev/null
+++ b/homepage/docs/options.md
@@ -0,0 +1,23 @@
+---
+layout: post
+title: "Advanced options"
+---
+
+<div class="md-content" markdown="1">{% include_absolute '../docs/options.md' %}</div>
+
+<style type="text/css">
+ .md-content h1 {
+ display: none;
+ }
+ .md-content h2 {
+ margin-bottom: 1rem;
+ }
+ .md-content h3 {
+ font-size: 1.5rem;
+ margin-bottom: 1rem;
+ }
+</style>
+
+<script>
+ document.querySelectorAll('.md-content table').forEach(el => el.classList.add('table'));
+</script>
diff --git a/homepage/index.html b/homepage/index.html
new file mode 100644
index 0000000..1d96378
--- /dev/null
+++ b/homepage/index.html
@@ -0,0 +1,111 @@
+---
+layout: default
+---
+
+<!-- Header-->
+<header class="bg-dark py-5">
+ <div class="container px-5">
+ <div class="row gx-5 align-items-center justify-content-center">
+ <div class="col-lg-8 col-xl-7 col-xxl-6">
+ <div class="my-5 text-center text-xl-start">
+ <h1 class="display-5 fw-bolder text-white mb-2">Advanced online election counting</h1>
+ <p class="lead fw-normal text-white-50 mb-4">Count instant runoff and single transferable vote elections for free, no downloads or sign-up required</p>
+ <div class="d-grid gap-3 d-sm-flex justify-content-sm-center justify-content-xl-start">
+ <a class="btn btn-primary btn-lg px-4 me-sm-3" href="{{ site.baseurl }}/stv/">Get Started</a>
+ <a class="btn btn-outline-light btn-lg px-4" href="{{ site.baseurl }}/about.html">Learn More</a>
+ </div>
+ </div>
+ </div>
+ <div class="col-xl-5 col-xxl-6 d-none d-xl-block text-center"><img class="img-fluid rounded-3 my-5" src="{{ site.baseurl }}/assets/headerimg.png" alt="Screenshot of OpenTally" /></div>
+ </div>
+ </div>
+</header>
+
+<!-- Features section-->
+<section class="py-5 bg-light" id="features">
+ <div class="container px-5 my-5">
+ <div class="row gx-5">
+ <div class="col-lg-4 mb-5 mb-lg-0"><h2 class="fw-bolder mb-0">Key features</h2></div>
+ <div class="col-lg-8">
+ <div class="row gx-5 row-cols-1 row-cols-md-2">
+ <div class="col mb-5 h-100">
+ <h2 class="h5">Runs in your browser</h2>
+ <p class="mb-0">No downloads or sign-ups are required. OpenTally counts are computed entirely inside your browser, and no data ever leaves your computer.</p>
+ </div>
+ <div class="col mb-5 h-100">
+ <h2 class="h5">Wide range of STV systems</h2>
+ <p class="mb-0">OpenTally supports Gregory (inclusive and exclusive, weighted and unweighted), Meek and Wright variants of the single transferable vote.</p>
+ </div>
+ <div class="col mb-5 mb-md-0 h-100">
+ <h2 class="h5">Support for arbitrary constraints</h2>
+ <p class="mb-0">OpenTally is the only publicly available election counting software to support arbitrary combinations of constraints, such as gender quotas and other affirmative action requirements.</p>
+ </div>
+ <div class="col h-100">
+ <h2 class="h5">Free and open source</h2>
+ <p class="mb-0">Source code for OpenTally is publicly available under the <a href="{{ site.git_url }}/tree/COPYING">GNU AGPLv3</a>.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</section>
+
+<!-- Blog preview section-->
+<section class="py-5">
+ <div class="container px-5 my-5">
+ <div class="row gx-5 justify-content-center">
+ <div class="col-lg-8 col-xl-6">
+ <div class="text-center">
+ <h2 class="fw-bolder">From our blog</h2>
+ <p class="lead fw-normal text-muted mb-5">Latest news and posts from the OpenTally blog</p>
+ </div>
+ </div>
+ </div>
+ <div class="row gx-5">
+ <div class="col-lg-4 mb-5">
+ <div class="card h-100 shadow border-0">
+ <div class="card-body p-4">
+ <div class="badge bg-primary bg-gradient rounded-pill mb-2">Dev Log</div>
+ <a class="text-decoration-none link-dark stretched-link" href="{{ site.url }}/blog/2021/08/21/stv-parcels.html"><h5 class="card-title mb-3">Parcels? Subparcels? Not just for STV hand counts!</h5></a>
+ <p class="card-text mb-0">Single transferable vote rules designed for hand-counting often contain references to ‘parcels’ (or ‘bundles’ or ‘batches’), ‘further parcels’ and sometimes even ‘subparcels’.</p>
+ <p>For example, consider the <a href="#">4th stage of the ERS97 model election</a>. 107 of Glazier's and Wright's ballot papers are aggregated according to…</p>
+ </div>
+ <div class="card-footer p-4 pt-0 bg-transparent border-top-0">
+ <div class="small text-muted">21 August 2021</div>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-4 mb-5">
+ <div class="card h-100 shadow border-0">
+ <div class="card-body p-4">
+ <div class="badge bg-primary bg-gradient rounded-pill mb-2">Dev Log</div>
+ <a class="text-decoration-none link-dark stretched-link" href="{{ site.url }}/blog/2021/07/30/blt-parser.html"><h5 class="card-title mb-3">Implementing a BLT parser by hand in Rust (vs pest and combine)</h5></a>
+ <p class="card-text"><a href="#">OpenTally</a> is open-source software which can count single transferable vote elections specified using the <a href="#">BLT file format</a>.</p>
+ <p class="card-text mb-0">Earlier this month, I <a href="#">replaced</a> OpenTally's previous naive <a href="#">string-manipulation-based BLT parser</a> with one using <a href="#">pest</a>. A new parser was necessary to support <a href="#">extensions to the BLT</a>…</p>
+ </div>
+ <div class="card-footer p-4 pt-0 bg-transparent border-top-0">
+ <div class="small text-muted">30 July 2021</div>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-4 mb-5">
+ <div class="card h-100 shadow border-0">
+ <div class="card-body p-4">
+ <div class="badge bg-primary bg-gradient rounded-pill mb-2">Dev Log</div>
+ <a class="text-decoration-none link-dark stretched-link" href="{{ site.url }}/blog/2021/07/28/asyncify-vanilla.html"><h5 class="card-title mb-3">Asyncify with vanilla JS/<wbr>WebAssembly (wasm-bindgen compatible)</h5></a>
+ <p class="card-text">WebAssembly is a technology for executing compiled programs in the web browser at near-native speeds. However, it has a number of current limitations, including that it does not support coroutines/<wbr>asynchronicity.</p>
+ <p class="card-text mb-0">In <a href="#">OpenTally</a>, WebAssembly is used to run code for counting an election. This…</p>
+ </div>
+ <div class="card-footer p-4 pt-0 bg-transparent border-top-0">
+ <div class="small text-muted">28 July 2021</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row gx-5 justify-content-center">
+ <div class="col-8 text-center">
+ <a class="btn btn-outline-primary btn-lg px-4" href="{{ site.url }}/blog/tag/opentally/">Read More</a>
+ </div>
+ </div>
+ </div>
+</section>
Contact (issues, pull requests, etc.) at git@yingtongli.me. Generated by cgit.