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 Conway et al. * [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 +--- + + + +
+

404

+ +

Page not found :(

+

The requested page could not be found.

+
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 @@ +--- +--- + + + + + + + {% if post.title %}{{ post.title }}{% else %}{{ site.title }}{% endif %} + + + + +
+ + + {{ content }} +
+ + + + + + 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 +--- + +
+
+

{{ page.title }}

+ + {{ content }} +
+
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! + (?[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+) + (?.*) + !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" +--- + +
{% include_absolute '../README.md' %}
+ + diff --git a/homepage/assets/headerimg.png b/homepage/assets/headerimg.png new file mode 100644 index 0000000..0aa52cf Binary files /dev/null and b/homepage/assets/headerimg.png 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" +--- + +
{% include_absolute '../docs/options.md' %}
+ + + + 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 +--- + + +
+
+
+
+
+

Advanced online election counting

+

Count instant runoff and single transferable vote elections for free, no downloads or sign-up required

+ +
+
+
Screenshot of OpenTally
+
+
+
+ + +
+
+
+

Key features

+
+
+
+

Runs in your browser

+

No downloads or sign-ups are required. OpenTally counts are computed entirely inside your browser, and no data ever leaves your computer.

+
+
+

Wide range of STV systems

+

OpenTally supports Gregory (inclusive and exclusive, weighted and unweighted), Meek and Wright variants of the single transferable vote.

+
+
+

Support for arbitrary constraints

+

OpenTally is the only publicly available election counting software to support arbitrary combinations of constraints, such as gender quotas and other affirmative action requirements.

+
+
+

Free and open source

+

Source code for OpenTally is publicly available under the GNU AGPLv3.

+
+
+
+
+
+
+ + +
+
+
+
+
+

From our blog

+

Latest news and posts from the OpenTally blog

+
+
+
+
+
+
+
+
Dev Log
+
Parcels? Subparcels? Not just for STV hand counts!
+

Single transferable vote rules designed for hand-counting often contain references to ‘parcels’ (or ‘bundles’ or ‘batches’), ‘further parcels’ and sometimes even ‘subparcels’.

+

For example, consider the 4th stage of the ERS97 model election. 107 of Glazier's and Wright's ballot papers are aggregated according to…

+
+ +
+
+
+
+
+
Dev Log
+
Implementing a BLT parser by hand in Rust (vs pest and combine)
+

OpenTally is open-source software which can count single transferable vote elections specified using the BLT file format.

+

Earlier this month, I replaced OpenTally's previous naive string-manipulation-based BLT parser with one using pest. A new parser was necessary to support extensions to the BLT

+
+ +
+
+
+
+
+
Dev Log
+
Asyncify with vanilla JS/WebAssembly (wasm-bindgen compatible)
+

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/asynchronicity.

+

In OpenTally, WebAssembly is used to run code for counting an election. This…

+
+ +
+
+
+
+
+ Read More +
+
+
+