Repository: pepkit/eido (language: python)

Issue:
list of available output formats
How can I get the list of availlable output formats?

```
nsheff@zither:~/code/bedshift_paper$ eido convert --help
usage: eido convert [-h] [--st-index ST_INDEX] -f FORMAT [-n SAMPLE_NAME [SAMPLE_NAME ...]]
                    [-a ARGS [ARGS ...]]
                    PEP

Convert a PEP using an available filter

positional arguments:
  PEP                   Path to a PEP configuration file in yaml format.

optional arguments:
  -h, --help            show this help message and exit
  --st-index ST_INDEX   Sample table index to use, samples are identified by 'sample_name' by default.
  -f FORMAT, --format FORMAT
                        Path to a PEP schema file in yaml format.
  -n SAMPLE_NAME [SAMPLE_NAME ...], --sample-name SAMPLE_NAME [SAMPLE_NAME ...]
                        Name of the samples to inspect.
  -a ARGS [ARGS ...], --args ARGS [ARGS ...]
                        Provide arguments to the filter function (e.g. arg1=val1 arg2=val2).

```

Also, is the `-f` command docs here correct? I think that's the wrong help string.
Processed PEP filter
We need to have a built-in filter that returns a processed PEP.

So, the input is, of course, a PEP. The output is a PEP that has run through sample modifiers and project modifiers -- that is, a *processed PEP*.

So, the user who calls the filter, if he wants to spit the output to files, needs to provide several output file paths; in fact, all the possible files that could go into a PEP: 
- project config
- sample table
- subsample table

See PR #38.

All the filter does is load the PEP with peppy, and then return the objects (as strings), which will have been processed.

@nleroy917 does this make sense to you?



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml
index f58e4c63..8b48ddf1 100644
--- a/.github/workflows/black.yml
+++ b/.github/workflows/black.yml
@@ -1,6 +1,6 @@
 name: Lint
 
-on: [push, pull_request]
+on: [pull_request]
 
 jobs:
   lint:
diff --git a/.github/workflows/run-codecov.yml b/.github/workflows/run-codecov.yml
new file mode 100644
index 00000000..a41a1fde
--- /dev/null
+++ b/.github/workflows/run-codecov.yml
@@ -0,0 +1,21 @@
+name: Run codecov
+
+on:
+  pull_request:
+    branches: [master]
+
+jobs:
+  pytest:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        python-version: [3.9]
+        os: [ubuntu-latest]
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Upload coverage to Codecov
+      uses: codecov/codecov-action@v2
+      with:
+        file: ./coverage.xml
+        name: py-${{ matrix.python-version }}-${{ matrix.os }}
diff --git a/.gitignore b/.gitignore
index 2a9c7631..f18bbd0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,7 +14,7 @@ doc/build/*
 # generic ignore list:
 *.lst
 
-# Compiled source 
+# Compiled source
 *.com
 *.class
 *.dll
@@ -22,8 +22,8 @@ doc/build/*
 *.o
 *.so
 *.pyc
- 
-# Packages 
+
+# Packages
 # it's better to unpack these files and commit the raw source
 # git has its own built in compression methods
 *.7z
@@ -34,13 +34,13 @@ doc/build/*
 *.rar
 *.tar
 *.zip
- 
-# Logs and databases 
+
+# Logs and databases
 *.log
 *.sql
 *.sqlite
- 
-# OS generated files 
+
+# OS generated files
 .DS_Store
 .DS_Store?
 ._*
@@ -49,7 +49,7 @@ doc/build/*
 ehthumbs.db
 Thumbs.db
 
-# Gedit temporary files 
+# Gedit temporary files
 *~
 
 # libreoffice lock files:
@@ -61,6 +61,7 @@ open_pipelines/
 
 # IDE-specific items
 .idea/
+.vscode/
 
 # pytest-related
 .cache/
@@ -81,3 +82,11 @@ peppy.egg-info/
 
 # mkdocs website
 site
+
+
+webeido/webeido/uploads
+
+# virtual env's
+.env
+env
+venv
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ab5489e2..c9137614 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v3.4.0
+    rev: v4.0.1
     hooks:
       - id: trailing-whitespace
       - id: check-yaml
@@ -9,12 +9,12 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/PyCQA/isort
-    rev: 5.7.0
+    rev: 5.9.1
     hooks:
       - id: isort
         args: ["--profile", "black"]
 
   - repo: https://github.com/psf/black
-    rev: 20.8b1
+    rev: 21.6b0
     hooks:
       - id: black
diff --git a/MANIFEST.in b/MANIFEST.in
index de89ce28..29209598 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,3 @@
 include requirements/*
 include README.md
-include LICENSE.txt
\ No newline at end of file
+include LICENSE.txt
diff --git a/README.md b/README.md
index 3398ca17..8ef1c23d 100644
--- a/README.md
+++ b/README.md
@@ -6,4 +6,3 @@
 [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 
 [PEP](http://pepkit.github.io) validation tool based on [jsonschema](https://github.com/Julian/jsonschema). See [documentation](http://eido.databio.org) for usage.
-
diff --git a/codecov.yml b/codecov.yml
index bc5f12fb..be72bc25 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -2,4 +2,4 @@ ignore:
   - "*/argparser.py"
   - "*/cli.py"
   - "*/__main__.py"
-  - "setup.py"
\ No newline at end of file
+  - "setup.py"
diff --git a/docs/README.md b/docs/README.md
index b08070fa..8b304309 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,15 +6,21 @@
 
 ## Introduction
 
-Eido is a validation and format conversion tool for [PEPs](http://pepkit.github.io). It provides validation based on [JSON Schema](https://github.com/Julian/jsonschema). The PEP specification defines a formal structure for organizing project and sample metadata, and eido provides a way to validate if data complies with that specification. Eido extends the JSON Schema vocabulary with PEP-specific features, like required input files. Eido also provides a command-line interface to convert a PEP input into a variety of outputs using [eido filters](filters.md), and includes ability to write [custom filters](writing-a-filter.md).
+Eido is used to 1) validate or 2) convert format of sample metadata. Sample metadata is stored according to the standard [PEP specification](https://pep.databio.org). For validation, eido is based on [JSON Schema](https://json-schema.org) and extends it with new features, like required input files. You can [write your own schema](writing-a-schema.md) for your pipeline and use eido to validate sample metadata. For conversion, [eido filters](filters.md) convert sample metadata input into any output format, including [custom filters](writing-a-filter.md).
 
 ## Why do we need eido?
 
-A PEP consists of metadata describing a set of items called *samples*. The metadata is divided into two-components: 1) sample-specific attributes; and 2) project attributes, which apply to all samples. A PEP follows a [formal specification](http://pep.databio.org) for formatting and organizing this data. Projects that follow the PEP specification can be read by a variety of PEP-compatible tools, which may require specific sample or project attributes. Eido is used to validate these specific required attributes.
+Data-intensive bioinformatics projects often include metadata describing a set of samples. When it comes to handling such sample metadata, there are two common challenges that eido solves:
 
-[JSON Schema](https://json-schema.org/) is a vocabulary that allows you to annotate and validate JSON documents. It's great for validating JSON documents, but alone it cannot validate a PEP, which has powerful portability features that go beyond a simple JSON document, so we require additional capability to validate it. Eido extends JSON Schema to add this capability, along with other features for validating sample metadata listed below.
+<img src="img/validation.svg" style="float:right; width:220px; margin-left:50px">
 
-## PEP-specific validation features
+- **Validation**. Tool authors use eido to specify and describe required input sample attributes. Input sample attributes are described with a schema, and eido validates the sample metadata to ensure it satisfies the tool's needs. Eido uses [JSON Schema](https://json-schema.org/), which annotates and validates JSON. JSON schema alone is great for validating JSON, but bioinformatics sample metadata is more complicated, so eido provides additional capability and features tailored to bioinformatics projects listed below. 
+
+<img src="img/conversion.svg" style="float:right; width:220px; margin-left:50px">
+
+- **Format conversion**. Tools often require sample metadata in a specific format. Eido filters take a metadata in standard PEP format and convert it to any desired output format. Filters can be either built-in or custom. This allows a single sample metadata source to be used for multiple downstream analyses.
+
+## Eido validation features
 
 An eido schema is written using the JSON Schema vocabulary, plus a few additional features:
 
@@ -32,8 +38,6 @@ An eido schema is written using the JSON Schema vocabulary, plus a few additiona
 
 ---
 
-## What does 'eido' mean?
+## Why the name 'eido'?
 
 *Eidos* is a Greek term meaning *form*, *essence*, or *type* (see Plato's [Theory of Forms](https://en.wikipedia.org/wiki/Theory_of_forms)). Schemas are analogous to *forms*, and eido tests claims that an instance is of a particular form. Eido also helps *change* forms using filters.
-
-
diff --git a/docs/autodoc_build/eido.md b/docs/autodoc_build/eido.md
index cf9c5211..30a6f95b 100644
--- a/docs/autodoc_build/eido.md
+++ b/docs/autodoc_build/eido.md
@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
 </script>
 
 <style>
-h3 .content {
+h3 .content { 
     padding-left: 22px;
     text-indent: -15px;
  }
@@ -18,7 +18,7 @@ h3 .hljs .content {
     martin-bottom: 0px;
 }
 h4 .content, table .content, p .content, li .content { margin-left: 30px; }
-h4 .content {
+h4 .content { 
     font-style: italic;
     font-size: 1em;
     margin-bottom: 0px;
@@ -110,95 +110,7 @@ Print inspection info: Project or, if sample_names argument is provided, matched
 
 
 
-```python
-def get_available_pep_filters()
-```
-
-Get a list of available target formats
-#### Returns:
-
-- `List[str]`:  a list of available formats
-
-
-
-
-```python
-def convert_project(prj, target_format, plugin_kwargs=None)
-```
-
-Convert a `peppy.Project` object to a selected format
-#### Parameters:
-
-- `prj` (`peppy.Project`):  a Project object to convert
-- `plugin_kwargs` (`dict`):  kwargs to pass to the plugin function
-- `target_format` (`str`):  the format to convert the Project object to
-
-
-#### Raises:
-
-- `EidoFilterError`:  if the requested filter is not defined
-
-
-
-
-```python
-def basic_pep_filter(p, **kwargs)
-```
-
-Basic PEP filter, that does not convert the Project object.
-
-This filter can save the PEP representation to file, if kwargs include `path`.
-#### Parameters:
-
-- `p` (`peppy.Project`):  a Project to run filter on
-
-
-
-
-```python
-def yaml_pep_filter(p, **kwargs)
-```
-
-YAML PEP filter, that returns Project object representation.
-
-This filter can save the YAML to file, if kwargs include `path`.
-#### Parameters:
-
-- `p` (`peppy.Project`):  a Project to run filter on
-
-
-
-
-```python
-def csv_pep_filter(p, **kwargs)
-```
-
-CSV PEP filter, that returns Sample object representations
-
-This filter can save the CSVs to files, if kwargs include
-`sample_table_path` and/or `subsample_table_path`.
-#### Parameters:
-
-- `p` (`peppy.Project`):  a Project to run filter on
-
-
-
-
-```python
-def yaml_samples_pep_filter(p, **kwargs)
-```
-
-YAML samples PEP filter, that returns only Sample object representations.
-
-This filter can save the YAML to file, if kwargs include `path`.
-#### Parameters:
-
-- `p` (`peppy.Project`):  a Project to run filter on
-
-
-
-
 
 
 
-*Version Information: `eido` v0.1.5-dev, generated by `lucidoc` v0.4.2*
+*Version Information: `eido` v0.1.0, generated by `lucidoc` v0.4.2*
\ No newline at end of file
diff --git a/docs/changelog.md b/docs/changelog.md
index 5e0b2f42..324ac639 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,14 +2,26 @@
 
 This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format.
 
-## [0.1.5] - 2021-04-15
+## [0.1.6] - 2022-05-16
 ### Added
+- a possibility to set a custom sample table index with `-s/--st-index` option
+- an option to see filters docs via CLI: `eido filters -f <filter_name>`
+- PEP filters now return their conversion result for progrommatic use.
+- PEP filters can write to files.
+- A filter can write multiple outputs to multiple files using the `paths` keyword arg.
+
+### Fixed
+- Some error messages with incorrectly defined schemas.
+- 'required' attribute is no longer required in schema
 
-** The PEP filters are an experimental feature and may change in feature versions of `eido`**
+### Changed
+- Moved all `eido filter` functionality into the `eido convert` command for simplicity. This way, a single top-level command namespace holds all related functionality. Filters are still EXPERIMENTAL.
 
-- `eido convert` command that converts the provided PEP to a specified format
-- `eido filter` command that lists available filters in current environment
-- built-in plugins:
+## [0.1.5] - 2021-04-15
+### Added
+- `eido convert` converts the provided PEP to a specified format (EXPERIMENTAL! may change in future versions)
+- `eido filter` lists available filters in current environment (EXPERIMENTAL! may change in future versions)
+- built-in plugins (EXPERIMENTAL! may change in future versions):
   - `basic_pep_filter`
   - `yaml_pep_filter`
   - `csv_pep_filter`
diff --git a/docs/contributing.md b/docs/contributing.md
index e73a6349..9508346b 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -1,6 +1,5 @@
 ## Contributing
 
 Pull requests or issues are welcome. After adding a new feature, please add tests in the `tests` folder and run the test suite. The only additional dependencies needed beyond those for the package can be installed with: `pip install -r requirements/requirements-dev.txt`.
-  
-Once those are installed, run the tests with `pytest` or `python setup.py test`.
 
+Once those are installed, run the tests with `pytest` or `python setup.py test`.
diff --git a/docs/example-schemas.md b/docs/example-schemas.md
index b693fb76..cfea4b4e 100644
--- a/docs/example-schemas.md
+++ b/docs/example-schemas.md
@@ -5,4 +5,4 @@ With `eido` you can create your own schema to describe the kind of projects your
 - [Generic PEP2.0.0 schema](http://schema.databio.org/pep/2.0.0.yaml) -- all PEPs should validate against this schema
 - [PEPPRO pipeline schema](http://schema.databio.org/pipelines/ProseqPEP.yaml) -- describes PEPs compatible with the [PEPPRO](http://peppro.databio.org) pipeline
 - [PEPATAC pipeline schema](http://schema.databio.org/pipelines/pepatac.yaml) -- describes PEPs compatible with the [PEPATAC](http://pepatac.databio.org) pipeline
-- [refgenie databio build schema](https://schema.databio.org/refgenie/refgenie_build.yaml) -- describes PEPs compatible with building [refgenie](http://refgenie.databio.org) assets
\ No newline at end of file
+- [refgenie databio build schema](https://schema.databio.org/refgenie/refgenie_build.yaml) -- describes PEPs compatible with building [refgenie](http://refgenie.databio.org) assets
diff --git a/docs/filters.md b/docs/filters.md
index 207f1940..21b064c1 100644
--- a/docs/filters.md
+++ b/docs/filters.md
@@ -1,15 +1,15 @@
-**The PEP filters are an experimental feature and may change in feature versions of `eido`**
+**Filters are an experimental feature and may change in future versions of `eido`.**
 
-# Eido filters
+# Using eido filters
 
-Eido provides a CLI to convert a PEP into different output formats. These include some built-in formats, like csv (which takes your spits out a processed csv file, with project/sample modified), yaml, and a few others. It also provides a plugin system so that you can write your own Python functions to provide custom output formats.
+Eido provides a CLI to convert a PEP into different output formats. These include some built-in formats, like _csv_ (which produces a processed csv file, with project/sample already modified), _yaml_, and a few others. It also provides a plugin system so that you can write your own Python functions to provide custom output formats. You access filters through the `eido convert` command.
 
 ## View available filters
 
-Run this command to see what filters are available:
+To list available filters:
 
 ```console
-eido filters
+eido convert --list
 ```
 
 You'll see some output like this. There are a few built-in filters available:
@@ -23,7 +23,7 @@ Available filters:
  - yaml-samples
 ```
 
-You can also [write a custom filters](writing-a-filter.md), which will write your PEP into whatever format you need.
+You can add to this list by [writing a custom filter](writing-a-filter.md), which will write your PEP into whatever format you need.
 
 ## Convert a PEP into an alternative format with a filter
 
@@ -46,10 +46,30 @@ eido convert config.yaml -f yaml
 
 This will output your samples in yaml format.
 
-### Parametrizing PEP conversions
+### Parametrizing filters
 
-The filter functions are parameterizable, so depending on the desired filter function design and required arguments you might need to provide them like this:
+Filter functions are parameterizable. Some filters may request or require parameters. To learn more about a filter's parameters, use `-d` or `--describe`: `eido convert -f <filter_name> -d`, which displays the plugin documentation. For example:
 
 ```console
-eido convert config.yaml -f yaml -a argument1=value1 argument2=value2
+eido convert -f yaml-samples -d
+
+    YAML samples PEP filter, that returns only Sample object representations.
+
+    This filter can save the YAML to file, if kwargs include `path`.
+
+    :param peppy.Project p: a Project to run filter on
+```
+
+In this case, the argument `path` can be provided as an output file. Like this: 
+
+```console
+eido convert config.yaml -f yaml-samples -a path=output.yaml
 ```
+
+More generally, the form to provide parameters is like this
+
+```console
+eido convert config.yaml -f <filter_name> -a argument1=value1 argument2=value2
+```
+
+
diff --git a/docs/img/conversion.svg b/docs/img/conversion.svg
new file mode 100644
index 00000000..552ba7ee
--- /dev/null
+++ b/docs/img/conversion.svg
@@ -0,0 +1,2065 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   sodipodi:docname="conversion.svg"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   version="1.1"
+   id="svg2"
+   height="48.700863"
+   width="91.458092">
+  <defs
+     id="defs4">
+    <rect
+       id="rect8094"
+       height="300.37643"
+       width="267.2688"
+       y="542.2005"
+       x="473.57208" />
+    <marker
+       style="overflow:visible"
+       id="TriangleOutM"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="TriangleOutM">
+      <path
+         inkscape:connector-curvature="0"
+         transform="scale(0.4)"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt"
+         d="M 5.77,0 -2.88,5 V -5 Z"
+         id="path4156" />
+    </marker>
+    <linearGradient
+       id="linearGradient892-3-0">
+      <stop
+         id="stop893-2-3"
+         offset="0.00000000"
+         style="stop-color:#ffffff;stop-opacity:0.00000000;" />
+      <stop
+         id="stop894-2-1"
+         offset="1"
+         style="stop-color:#fff;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4492"
+       y2="89"
+       gradientUnits="userSpaceOnUse"
+       y1="20"
+       x2="100"
+       x1="35">
+      <stop
+         id="stop4488"
+         stop-color="#00c0c0"
+         stop-opacity=".75294"
+         offset="0" />
+      <stop
+         id="stop4490"
+         stop-color="#80f5f5"
+         stop-opacity=".30196"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3161">
+      <stop
+         id="stop3163"
+         stop-color="#fff"
+         offset="0" />
+      <stop
+         id="stop3165"
+         stop-color="#fff"
+         stop-opacity="0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="108.87218"
+       y1="108.87218"
+       xlink:href="#linearGradient15251"
+       x2="292.65842"
+       x1="285.47037"
+       id="linearGradient15378"
+       gradientTransform="matrix(0.89839544,0,0,1.4262987,-2583.0381,2966.5502)" />
+    <linearGradient
+       id="linearGradient15251">
+      <stop
+         stop-color="#11ffff"
+         offset="0"
+         id="stop15252" />
+      <stop
+         stop-color="#e1ebff"
+         stop-opacity="0"
+         offset="1"
+         id="stop15253" />
+    </linearGradient>
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="186.81477"
+       y1="186.81477"
+       xlink:href="#linearGradient15251"
+       x2="244.25108"
+       x1="239.22408"
+       id="linearGradient15379"
+       gradientTransform="matrix(1.2750153,0,0,1.0049921,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="111.47942"
+       y1="111.47942"
+       xlink:href="#linearGradient15251"
+       x2="311.97397"
+       x1="305.36746"
+       id="linearGradient15380"
+       gradientTransform="matrix(1.148607,0,0,1.115595,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="270.58075"
+       y1="270.58075"
+       xlink:href="#linearGradient15251"
+       x2="271.39532"
+       x1="266.74103"
+       id="linearGradient15383"
+       gradientTransform="matrix(1.5031099,0,0,0.85248603,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="212.56871"
+       y1="212.56871"
+       xlink:href="#linearGradient15251"
+       x2="175.87711"
+       x1="169.62802"
+       id="linearGradient15384"
+       gradientTransform="matrix(1.149307,0,0,1.1149155,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="220.00958"
+       y1="220.00958"
+       xlink:href="#linearGradient15251"
+       x2="215.18179"
+       x1="208.20387"
+       id="linearGradient15385"
+       gradientTransform="matrix(0.9480899,0,0,1.3515387,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="264.51785"
+       y1="264.51785"
+       xlink:href="#linearGradient15251"
+       x2="219.02859"
+       x1="216.48724"
+       id="linearGradient15386"
+       gradientTransform="matrix(1.6080623,0,0,0.79684736,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="529.10437"
+       y1="529.10437"
+       xlink:href="#linearGradient15251"
+       x2="186.3316"
+       x1="184.40688"
+       id="linearGradient15387"
+       gradientTransform="matrix(1.847117,0,0,0.69371905,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="245.84831"
+       y1="245.84831"
+       xlink:href="#linearGradient15251"
+       x2="315.26337"
+       x1="307.05576"
+       id="linearGradient15388"
+       gradientTransform="matrix(0.92408916,0,0,1.3866413,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="168.36928"
+       y1="168.36928"
+       xlink:href="#linearGradient15251"
+       x2="630.80371"
+       x1="622.69678"
+       id="linearGradient15389"
+       gradientTransform="matrix(0.72507885,0,0,1.7672288,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="234.81921"
+       y1="234.81921"
+       xlink:href="#linearGradient15251"
+       x2="495.96729"
+       x1="491.17938"
+       id="linearGradient15390"
+       gradientTransform="matrix(0.93320216,0,0,1.3731004,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="225.18878"
+       y1="225.18878"
+       xlink:href="#linearGradient15251"
+       x2="535.78436"
+       x1="527.91168"
+       id="linearGradient15391"
+       gradientTransform="matrix(0.97200857,0,0,1.3182808,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="238.44827"
+       y1="238.44827"
+       xlink:href="#linearGradient15251"
+       x2="206.7001"
+       x1="204.62263"
+       id="linearGradient15392"
+       gradientTransform="matrix(1.2300616,0,0,1.0417204,-2583.0381,2966.5502)" />
+    <linearGradient
+       id="E">
+      <stop
+         id="R"
+         stop-color="#2e3436" />
+      <stop
+         id="S"
+         offset="1"
+         stop-color="#2e3436"
+         stop-opacity="0" />
+    </linearGradient>
+    <linearGradient
+       id="D">
+      <stop
+         id="P"
+         stop-color="#eeeeec" />
+      <stop
+         id="Q"
+         offset="1"
+         stop-color="#d3d7cf" />
+    </linearGradient>
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-259.90161,564.66242)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-129.81603,374.91355)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-249.83623,466.9785)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-332.79973,444.08618)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-249.83623,466.61717)"
+       x1="34.130001"
+       y1="8.75"
+       x2="36.533001"
+       y2="6.3629999"
+       id="J"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-281.84156,543.48915)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-151.75598,353.74028)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-271.77618,445.80523)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-354.73969,422.91291)" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0-3"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0-5" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5-6"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(3.2895963,0,0,0.26843569,123.83968,345.41502)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3-2"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,284.67246,110.81677)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,136.28408,224.64237)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7-9"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4619"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4621" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9-1"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,33.711276,196.33917)" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-2"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-7" />
+    </filter>
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-0"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,496.7991,197.14557)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.9711)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-9"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4688"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4690" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-3"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,245.83788,282.66792)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.52437)"
+       x1="34.130001"
+       y1="8.75"
+       x2="36.533001"
+       y2="6.3629999"
+       id="J-6"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="26.648937"
+       y1="20.603781"
+       x2="135.66525"
+       y2="114.39767"
+       id="linearGradient1478"
+       xlink:href="#linearGradient4689"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" />
+    <linearGradient
+       id="linearGradient4689">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop4691" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop4693" />
+    </linearGradient>
+    <linearGradient
+       x1="150.96111"
+       y1="192.35176"
+       x2="112.03144"
+       y2="137.27299"
+       id="linearGradient1475"
+       xlink:href="#linearGradient4671"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" />
+    <linearGradient
+       id="linearGradient4671">
+      <stop
+         style="stop-color:#ffd43b;stop-opacity:1"
+         offset="0"
+         id="stop4673" />
+      <stop
+         style="stop-color:#ffe873;stop-opacity:1"
+         offset="1"
+         id="stop4675" />
+    </linearGradient>
+    <radialGradient
+       cx="61.518883"
+       cy="132.28575"
+       r="29.036913"
+       fx="61.518883"
+       fy="132.28575"
+       id="radialGradient1480"
+       xlink:href="#linearGradient2795"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.382716e-8,-0.296405,1.43676,4.683673e-7,-128.544,150.5202)" />
+    <linearGradient
+       id="linearGradient2795">
+      <stop
+         style="stop-color:#b8b8b8;stop-opacity:0.49803922"
+         offset="0"
+         id="stop2797" />
+      <stop
+         style="stop-color:#7f7f7f;stop-opacity:0"
+         offset="1"
+         id="stop2799" />
+    </linearGradient>
+    <linearGradient
+       id="gradientFill-1"
+       x1="0.7414425"
+       x2="590.86261"
+       y1="3.6658268"
+       y2="593.78699"
+       gradientUnits="userSpaceOnUse"
+       spreadMethod="pad"
+       gradientTransform="matrix(0.11494196,0,0,0.07697987,293.05555,752.18406)">
+      <stop
+         offset="0"
+         stop-color="rgb(203,206,208)"
+         stop-opacity="1"
+         id="stop7" />
+      <stop
+         offset="1"
+         stop-color="rgb(132,131,139)"
+         stop-opacity="1"
+         id="stop9" />
+    </linearGradient>
+    <linearGradient
+       id="gradientFill-2"
+       x1="301.02603"
+       x2="703.0675"
+       y1="151.4006"
+       y2="553.44208"
+       gradientUnits="userSpaceOnUse"
+       spreadMethod="pad"
+       gradientTransform="matrix(0.09311939,0,0,0.09502015,293.05555,752.18406)">
+      <stop
+         offset="0"
+         stop-color="rgb(39,109,195)"
+         stop-opacity="1"
+         id="stop12" />
+      <stop
+         offset="1"
+         stop-color="rgb(22,92,170)"
+         stop-opacity="1"
+         id="stop14" />
+    </linearGradient>
+    <circle
+       id="f"
+       cx="16"
+       cy="16"
+       r="16" />
+    <polygon
+       id="d"
+       points="21.394,0 28.082,6.689 28.082,32 3.918,32 3.918,0 " />
+    <path
+       inkscape:connector-curvature="0"
+       id="b"
+       d="M 26.082,32 H 5.917 c -1.1,0 -2,-0.9 -2,-2 V 2 c 0,-1.1 0.9,-2 2,-2 h 20.165 c 1.1,0 2,0.9 2,2 v 28 c 0,1.1 -0.9,2 -2,2 z" />
+    <style
+       type="text/css"
+       id="style6">
+   
+    .fil1 {fill:none}
+    .fil0 {fill:black}
+   
+  </style>
+    <rect
+       id="rect1364"
+       height="248.14047"
+       width="547.18158"
+       y="295.46783"
+       x="434.12497" />
+    <marker
+       style="overflow:visible"
+       id="TriangleOutM-4"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="TriangleOutM">
+      <path
+         inkscape:connector-curvature="0"
+         transform="scale(0.4)"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt"
+         d="M 5.77,0 -2.88,5 V -5 Z"
+         id="path4156-9" />
+    </marker>
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-9">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-6" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-0"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-259.90161,564.66242)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-2"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-129.81603,374.91355)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-249.83623,466.9785)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-8"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-4"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-332.79973,444.08618)" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0-4">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0-8" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5-0"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-281.84156,543.48915)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3-1"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-151.75598,353.74028)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-271.77618,445.80523)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7-5"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9-11"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-354.73969,422.91291)" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0-3-1">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0-5-5" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5-6-1"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(3.2895963,0,0,0.26843569,123.83968,345.41502)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3-2-7"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,284.67246,110.81677)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,136.28408,224.64237)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7-9-0"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4619-8">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4621-5" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9-1-3"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,33.711276,196.33917)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#D"
+       id="radialGradient5032"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,848.22767,302.28895)"
+       cx="13.559"
+       cy="12.06"
+       r="16.219" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient892-3-0"
+       id="linearGradient5034"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,699.83932,416.11451)"
+       y1="2.438"
+       x2="0"
+       y2="43.34" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-2-7">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-7-9" />
+    </filter>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#D"
+       id="radialGradient5036"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,597.26643,387.81133)"
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-0-6"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,496.7991,197.14557)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.9711)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-9-3"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4688-1">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4690-5" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-3-8"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,245.83788,282.66792)" />
+    <radialGradient
+       cx="61.518883"
+       cy="132.28575"
+       r="29.036913"
+       fx="61.518883"
+       fy="132.28575"
+       id="radialGradient1480-3"
+       xlink:href="#linearGradient2795"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.382716e-8,-0.296405,1.43676,4.683673e-7,-128.544,150.5202)" />
+    <radialGradient
+       r="16.219"
+       cy="12.06"
+       cx="13.559"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,848.22767,302.28895)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6720"
+       xlink:href="#D"
+       inkscape:collect="always" />
+    <linearGradient
+       y2="43.34"
+       x2="0"
+       y1="2.438"
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,699.83932,416.11451)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient6722"
+       xlink:href="#linearGradient892-3-0"
+       inkscape:collect="always" />
+    <radialGradient
+       r="3.03"
+       cy="8.6090002"
+       cx="34.130001"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,597.26643,387.81133)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6724"
+       xlink:href="#D"
+       inkscape:collect="always" />
+    <radialGradient
+       r="16.219"
+       cy="12.06"
+       cx="13.559"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,496.7991,197.14557)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6730"
+       xlink:href="#D"
+       inkscape:collect="always" />
+    <linearGradient
+       y2="43.34"
+       x2="0"
+       y1="2.438"
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.9711)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient6732"
+       xlink:href="#linearGradient892-3-0"
+       inkscape:collect="always" />
+    <radialGradient
+       r="3.03"
+       cy="8.6090002"
+       cx="34.130001"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,245.83788,282.66792)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6734"
+       xlink:href="#D"
+       inkscape:collect="always" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:document-rotation="0"
+     inkscape:snap-global="false"
+     units="mm"
+     fit-margin-bottom="2"
+     fit-margin-right="2"
+     fit-margin-left="2"
+     fit-margin-top="2"
+     inkscape:window-maximized="1"
+     inkscape:window-y="231"
+     inkscape:window-x="1272"
+     inkscape:window-height="1343"
+     inkscape:window-width="2488"
+     showgrid="false"
+     inkscape:current-layer="layer1"
+     inkscape:document-units="px"
+     inkscape:cy="59.523301"
+     inkscape:cx="30.433156"
+     inkscape:zoom="6.5074348"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="1"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     transform="translate(-192.00585,-461.87794)"
+     id="layer1"
+     inkscape:groupmode="layer"
+     inkscape:label="Layer 1">
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1330"
+       display="none"
+       style="display:none">
+      <g
+         id="g1326"
+         display="inline"
+         style="display:inline">
+        <polygon
+           id="polygon1316"
+           points="5.08,32 5.08,0 22.556,0 29.245,6.689 29.245,32 "
+           style="fill:#000000" />
+        <polygon
+           id="polygon1318"
+           points="22.555,0 29.245,6.689 22.555,6.689 "
+           style="fill:#000000" />
+        <g
+           id="g1324">
+          <rect
+             id="rect1320"
+             height="8.118"
+             width="24.093"
+             y="18.313"
+             x="2.7550001"
+             style="fill:#000000" />
+          <polygon
+             id="polygon1322"
+             points="2.755,26.431 5.08,28.755 5.08,26.431 "
+             style="fill:#000000" />
+        </g>
+      </g>
+      <text
+         id="text1328"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(4.9424,24.8354)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline;fill:#000000">HTML</text>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1334"
+       display="none"
+       style="display:none">
+      <text
+         id="text1332"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(5.7757,24.8354)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline;fill:#000000">HTML</text>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1350"
+       display="none"
+       style="display:none">
+      <g
+         id="g1346"
+         display="inline"
+         style="display:inline">
+        <polygon
+           id="polygon1336"
+           points="5.08,32 5.08,0 22.556,0 29.245,6.689 29.245,32 "
+           style="fill:#000000" />
+        <polygon
+           id="polygon1338"
+           points="22.555,0 29.245,6.689 22.555,6.689 "
+           style="fill:#000000" />
+        <g
+           id="g1344">
+          <polygon
+             id="polygon1340"
+             points="32,18.313 26.848,26.431 2.755,26.431 2.755,18.313 "
+             style="fill:#000000" />
+          <polygon
+             id="polygon1342"
+             points="2.755,26.431 5.08,28.755 5.08,26.431 "
+             style="fill:#000000" />
+        </g>
+      </g>
+      <text
+         id="text1348"
+         font-size="7px"
+         display="inline"
+         transform="translate(6.3452,24.7163)"
+         style="font-size:7px;line-height:0%;font-family:EurostileBold;display:inline;fill:#000000">HTML</text>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1382"
+       display="none"
+       style="display:none">
+      <g
+         id="g1364"
+         display="inline"
+         style="display:inline">
+        <circle
+           id="circle1352"
+           r="16"
+           cy="16"
+           cx="16"
+           style="fill:#000000" />
+        <g
+           id="g1362">
+          <defs
+             id="defs1355">
+            <circle
+               r="16"
+               cy="16"
+               cx="16"
+               id="circle1566" />
+          </defs>
+          <clipPath
+             id="e">
+            <use
+               id="use1357"
+               overflow="visible"
+               xlink:href="#f"
+               style="overflow:visible"
+               x="0"
+               y="0"
+               width="616.53546"
+               height="212.59842" />
+          </clipPath>
+          <polygon
+             id="polygon1360"
+             points="16.216,33.948 8.268,26 23.417,10.181 33.884,20.648 33.884,32.886 "
+             clip-path="url(#e)"
+             style="fill:#000000" />
+        </g>
+      </g>
+      <g
+         id="g1380"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1376">
+          <polygon
+             id="polygon1366"
+             points="8.314,26 8.314,6 19.237,6 23.417,10.181 23.417,26 "
+             style="fill:#000000" />
+          <polygon
+             id="polygon1368"
+             points="19.236,6 23.417,10.181 19.236,10.181 "
+             style="fill:#000000" />
+          <g
+             id="g1374">
+            <polygon
+               id="polygon1370"
+               points="25.139,17.446 21.919,22.519 6.861,22.519 6.861,17.446 "
+               style="fill:#000000" />
+            <polygon
+               id="polygon1372"
+               points="6.861,22.519 8.314,23.972 8.314,22.519 "
+               style="fill:#000000" />
+          </g>
+        </g>
+        <text
+           id="text1378"
+           font-size="4.375px"
+           transform="translate(9.1048,21.4479)"
+           style="font-size:4.375px;line-height:0%;font-family:EurostileBold;fill:#000000">HTML</text>
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1422"
+       display="none"
+       style="display:none">
+      <g
+         id="g1420"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1408">
+          <g
+             id="g1394">
+            <g
+               id="g1386">
+              <path
+                 id="path1384"
+                 d="m 5.917,31.516 c -0.827,0 -1.5,-0.673 -1.5,-1.5 v -28 c 0,-0.827 0.673,-1.5 1.5,-1.5 h 20.165 c 0.827,0 1.5,0.673 1.5,1.5 v 28 c 0,0.827 -0.673,1.5 -1.5,1.5 z"
+                 inkscape:connector-curvature="0"
+                 style="fill:#000000" />
+            </g>
+            <g
+               id="g1392">
+              <g
+                 id="g1390">
+                <path
+                   id="path1388"
+                   d="m 26.082,1.016 c 0.551,0 1,0.449 1,1 v 28 c 0,0.551 -0.449,1 -1,1 H 5.918 c -0.551,0 -1,-0.449 -1,-1 v -28 c 0,-0.551 0.449,-1 1,-1 h 20.164 m 0,-1 H 5.918 c -1.1,0 -2,0.9 -2,2 v 28 c 0,1.1 0.9,2 2,2 h 20.165 c 1.1,0 2,-0.9 2,-2 v -28 c -10e-4,-1.1 -0.901,-2 -2.001,-2 z"
+                   inkscape:connector-curvature="0"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+          <g
+             id="g1402">
+            <g
+               id="g1400">
+              <g
+                 id="g1398">
+                <rect
+                   id="rect1396"
+                   height="8.118"
+                   width="24.165001"
+                   y="18.329"
+                   x="3.918"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+          <g
+             id="g1406">
+            <polyline
+               id="polyline1404"
+               points="3.918,26.408 3.918,18.329 11.996,18.329     "
+               style="fill:#000000" />
+          </g>
+        </g>
+        <g
+           id="g1418">
+          <path
+             id="path1410"
+             d="m 10.771,19.914 v 4.922 H 9.837 V 22.708 H 7.529 v 2.128 H 6.595 v -4.922 h 0.934 v 2.008 h 2.308 v -2.008 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1412"
+             d="m 13.599,20.75 v 4.086 H 12.665 V 20.75 h -1.421 v -0.836 h 3.826 v 0.836 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1414"
+             d="m 21.297,19.914 v 4.922 h -0.934 v -2.683 c 0,-0.214 0.006,-0.457 0.018,-0.729 l 0.018,-0.368 0.02,-0.364 H 20.39 l -0.111,0.343 -0.109,0.343 c -0.102,0.308 -0.18,0.535 -0.234,0.682 l -1.082,2.776 h -0.85 L 16.91,22.081 C 16.85,21.93 16.771,21.702 16.672,21.399 L 16.56,21.056 16.448,20.717 h -0.029 l 0.018,0.357 0.018,0.36 c 0.014,0.278 0.021,0.518 0.021,0.718 v 2.683 h -0.934 v -4.922 h 1.522 l 0.88,2.278 c 0.061,0.159 0.139,0.386 0.238,0.682 l 0.107,0.343 0.111,0.339 h 0.033 l 0.105,-0.339 0.107,-0.339 c 0.09,-0.286 0.166,-0.512 0.23,-0.678 l 0.865,-2.286 h 1.557 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1416"
+             d="m 23.097,19.914 v 4.085 h 2.308 v 0.837 h -3.242 v -4.922 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+        </g>
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1460"
+       display="none"
+       style="display:none">
+      <g
+         id="g1446"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1426">
+          <circle
+             id="circle1424"
+             r="16"
+             cy="16"
+             cx="16"
+             style="fill:#000000" />
+        </g>
+        <g
+           id="g1444">
+          <g
+             id="g1434">
+            <g
+               id="g1432">
+              <g
+                 id="g1430">
+                <path
+                   id="path1428"
+                   d="M 22.302,6 H 9.698 C 9.01,6 8.448,6.562 8.448,7.25 v 17.5 c 0,0.688 0.562,1.25 1.25,1.25 h 12.603 c 0.687,0 1.25,-0.562 1.25,-1.25 V 7.25 C 23.552,6.562 22.989,6 22.302,6 Z"
+                   inkscape:connector-curvature="0"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+          <g
+             id="g1442">
+            <g
+               id="g1440">
+              <g
+                 id="g1438">
+                <rect
+                   id="rect1436"
+                   height="5.0739999"
+                   width="15.103"
+                   y="17.445999"
+                   x="8.448"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+        </g>
+      </g>
+      <g
+         id="g1458"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1456">
+          <path
+             id="path1448"
+             d="m 12.857,18.436 v 3.076 h -0.583 v -1.33 h -1.443 v 1.33 h -0.584 v -3.076 h 0.584 v 1.255 h 1.443 v -1.255 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1450"
+             d="m 14.624,18.959 v 2.554 H 14.04 v -2.554 h -0.888 v -0.522 h 2.391 v 0.522 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1452"
+             d="m 19.436,18.436 v 3.076 h -0.583 v -1.677 c 0,-0.134 0.004,-0.286 0.011,-0.455 l 0.011,-0.23 0.012,-0.228 h -0.018 l -0.07,0.214 -0.068,0.214 c -0.063,0.192 -0.112,0.334 -0.146,0.426 l -0.676,1.735 h -0.531 l -0.684,-1.722 c -0.038,-0.095 -0.087,-0.237 -0.149,-0.426 l -0.07,-0.214 -0.07,-0.212 h -0.018 l 0.011,0.223 0.011,0.225 c 0.009,0.174 0.013,0.323 0.013,0.449 v 1.677 h -0.584 v -3.076 h 0.951 l 0.55,1.424 c 0.038,0.099 0.087,0.241 0.149,0.426 l 0.067,0.214 0.07,0.212 h 0.021 l 0.066,-0.212 0.067,-0.212 c 0.056,-0.179 0.104,-0.32 0.144,-0.424 l 0.541,-1.429 h 0.972 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1454"
+             d="m 20.561,18.436 v 2.553 h 1.442 v 0.523 h -2.026 v -3.076 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+        </g>
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1510"
+       display="none"
+       style="display:none">
+      <g
+         id="g1498"
+         display="inline"
+         style="display:inline">
+        <path
+           id="path1462"
+           d="M 26.082,32 H 5.917 c -1.1,0 -2,-0.9 -2,-2 V 2 c 0,-1.1 0.9,-2 2,-2 h 20.165 c 1.1,0 2,0.9 2,2 v 28 c 0,1.1 -0.9,2 -2,2 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <g
+           id="g1484">
+          <g
+             id="g1482">
+            <defs
+               id="defs1465">
+              <polygon
+                 points="3.918,32 3.918,0 21.394,0 28.082,6.689 28.082,32 "
+                 id="polygon1623" />
+            </defs>
+            <clipPath
+               id="c">
+              <use
+                 id="use1467"
+                 overflow="visible"
+                 xlink:href="#d"
+                 style="overflow:visible"
+                 x="0"
+                 y="0"
+                 width="616.53546"
+                 height="212.59842" />
+            </clipPath>
+            <g
+               id="g1480"
+               clip-path="url(#c)">
+              <rect
+                 id="rect1470"
+                 height="9.3380003"
+                 width="25.02"
+                 y="17.745001"
+                 x="3.493"
+                 style="fill:#000000" />
+              <g
+                 id="g1474">
+                <rect
+                   id="rect1472"
+                   height="0.5"
+                   width="25.025999"
+                   y="17.431999"
+                   x="3.487"
+                   style="fill:#000000" />
+              </g>
+              <g
+                 id="g1478">
+                <rect
+                   id="rect1476"
+                   height="0.5"
+                   width="25.025999"
+                   y="26.833"
+                   x="3.487"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+        </g>
+        <g
+           id="g1494">
+          <defs
+             id="defs1487">
+            <path
+               d="M 26.082,32 H 5.917 c -1.1,0 -2,-0.9 -2,-2 V 2 c 0,-1.1 0.9,-2 2,-2 h 20.165 c 1.1,0 2,0.9 2,2 v 28 c 0,1.1 -0.9,2 -2,2 z"
+               id="path1636"
+               inkscape:connector-curvature="0" />
+          </defs>
+          <clipPath
+             id="a">
+            <use
+               id="use1489"
+               overflow="visible"
+               xlink:href="#b"
+               style="overflow:visible"
+               x="0"
+               y="0"
+               width="616.53546"
+               height="212.59842" />
+          </clipPath>
+          <polyline
+             id="polyline1492"
+             points="21.344,-0.234 28.406,-0.234 28.406,6.828    "
+             clip-path="url(#a)"
+             style="fill:#000000" />
+        </g>
+        <polygon
+           id="polygon1496"
+           points="21.729,0 21.021,0 28.083,7.061 28.083,6.354 "
+           style="fill:#000000" />
+      </g>
+      <g
+         id="g1508"
+         display="inline"
+         style="display:inline">
+        <path
+           id="path1500"
+           d="m 10.771,19.914 v 4.922 H 9.837 V 22.708 H 7.529 v 2.128 H 6.595 v -4.922 h 0.934 v 2.008 h 2.308 v -2.008 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <path
+           id="path1502"
+           d="m 13.599,20.75 v 4.086 H 12.665 V 20.75 h -1.421 v -0.836 h 3.826 v 0.836 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <path
+           id="path1504"
+           d="m 21.297,19.914 v 4.922 h -0.934 v -2.683 c 0,-0.214 0.006,-0.457 0.018,-0.729 l 0.018,-0.368 0.02,-0.364 H 20.39 l -0.111,0.343 -0.109,0.343 c -0.102,0.308 -0.18,0.535 -0.234,0.682 l -1.082,2.776 h -0.85 L 16.91,22.081 C 16.85,21.93 16.771,21.702 16.672,21.399 L 16.56,21.056 16.448,20.717 h -0.029 l 0.018,0.357 0.018,0.36 c 0.014,0.278 0.021,0.518 0.021,0.718 v 2.683 h -0.934 v -4.922 h 1.522 l 0.88,2.278 c 0.061,0.159 0.139,0.386 0.238,0.682 l 0.107,0.343 0.111,0.339 h 0.033 l 0.105,-0.339 0.107,-0.339 c 0.09,-0.286 0.166,-0.512 0.23,-0.678 l 0.865,-2.286 h 1.557 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <path
+           id="path1506"
+           d="m 23.097,19.914 v 4.085 h 2.308 v 0.837 h -3.242 v -4.922 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1516"
+       display="none"
+       style="display:none">
+      <text
+         id="text1512"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(4.9424,24.2963)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline">HTML</text>
+      <path
+         id="path1514"
+         d="M 22.556,0 H 5.08 V 17.313 H 2.755 v 9.118 0 l 2.324,2.324 v -2.324 0 V 32 H 29.244 V 6.689 Z M 26.83,6.189 H 23.054 V 2.414 Z M 3.755,18.313 h 22.093 v 7.118 H 3.755 Z M 28.245,31 H 6.08 V 26.431 H 26.848 V 17.313 H 6.08 V 1 h 15.974 v 6.189 h 6.19 V 31 Z"
+         display="inline"
+         inkscape:connector-curvature="0"
+         style="display:inline" />
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1534"
+       display="none"
+       style="display:none">
+      <text
+         id="text1530"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(4.9424,24.2963)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline">HTML</text>
+      <path
+         id="path1532"
+         d="M 22.556,0 H 5.08 V 17.313 H 2.755 v 9.118 0 l 2.324,2.324 v -2.324 0 V 32 H 29.244 V 6.689 Z M 26.83,6.189 H 23.054 V 2.414 Z M 3.755,18.313 h 22.093 v 7.118 H 3.755 Z"
+         display="inline"
+         inkscape:connector-curvature="0"
+         style="display:inline" />
+    </g>
+    <g
+       transform="matrix(-0.31969042,-0.55372006,0.55372006,-0.31969042,268.40661,479.18376)"
+       id="g1838"
+       style="fill-rule:evenodd">
+      <g
+         style="fill:#000000;fill-rule:evenodd"
+         id="g1836"
+         transform="matrix(0,0.46327147,-0.46327147,0,349.99468,70.425726)">
+        <g
+           transform="translate(-273.13876,667.95829)"
+           id="g1834"
+           style="fill:#000000">
+          <path
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
+             d="m 93.558594,52.710938 v 5.22559 H 81.091797 v 4.841788 h 12.466797 v 5.227543 l 6.623046,-3.824218 6.62109,-3.824219 -6.62109,-3.822266 z"
+             transform="matrix(2.1585616,0,0,2.1585616,-155.44878,-106.41793)"
+             id="path1832"
+             inkscape:connector-curvature="0"
+             sodipodi:nodetypes="cccccccccc" />
+        </g>
+      </g>
+    </g>
+    <g
+       id="g3118"
+       transform="translate(5.9840976,21.983912)">
+      <text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9px;line-height:0%;font-family:Arial;-inkscape-font-specification:Arial;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:middle;fill:#000001;fill-opacity:1;stroke:none;stroke-width:0.80025005;marker:none"
+         x="223.62106"
+         y="482.91251"
+         id="text1298"><tspan
+           style="font-size:8.0000248px;line-height:1;stroke-width:0.80025005"
+           sodipodi:role="line"
+           id="tspan1296"
+           x="223.62106"
+           y="482.91251">PEP</tspan></text>
+      <g
+         transform="matrix(0.51090381,0,0,0.51090381,-39.396681,433.22104)"
+         id="g7655">
+        <rect
+           transform="rotate(-90)"
+           y="445.97046"
+           x="-78.00325"
+           height="26.577616"
+           width="36.788246"
+           id="rect7506"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#000000;stroke-width:1.68163145;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+        <text
+           xml:space="preserve"
+           style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:monospace;-inkscape-font-specification:Monospace;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate"
+           x="460.61572"
+           y="26.735233"
+           id="text7508"><tspan
+             style="font-size:9px;line-height:1.25;font-family:monospace;text-align:center;text-anchor:middle"
+             sodipodi:role="line"
+             id="tspan7510"
+             x="460.61572"
+             y="26.735233">project</tspan><tspan
+             style="font-size:9px;line-height:1.25;font-family:monospace;text-align:center;text-anchor:middle"
+             sodipodi:role="line"
+             x="460.61572"
+             y="37.985233"
+             id="tspan1205">yaml</tspan></text>
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 448.74579,44.69502 h 10.04619"
+           id="path7518"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7520"
+           d="m 452.43501,47.1545 h 10.04618"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 452.43501,49.61397 h 10.04618"
+           id="path7522"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7525"
+           d="m 448.74579,52.07345 h 10.04619"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 452.43501,54.53292 h 10.04618"
+           id="path7527"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7529"
+           d="m 448.74579,56.9924 h 10.04619"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 452.43501,59.45188 h 10.04618"
+           id="path7531"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7533"
+           d="m 454.89448,61.91135 h 10.04619"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 448.74579,69.28978 h 10.04619"
+           id="path7535"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7537"
+           d="m 451.20527,71.74925 h 10.04618"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 454.89448,64.37083 h 10.04619"
+           id="path7539"
+           inkscape:connector-curvature="0" />
+        <g
+           transform="translate(-275.32507,-115.41405)"
+           id="g7541">
+          <rect
+             y="161.58936"
+             x="752.09998"
+             height="26.577616"
+             width="36.788246"
+             id="rect7545"
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#000000;stroke-width:1.68163145;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+          <text
+             xml:space="preserve"
+             style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:monospace;-inkscape-font-specification:Monospace;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate"
+             x="762.16205"
+             y="158.8647"
+             id="text7547"><tspan
+               style="font-size:9px;line-height:1.25;font-family:monospace"
+               sodipodi:role="line"
+               id="tspan7549"
+               x="762.16205"
+               y="158.8647">csv</tspan></text>
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,164.82616 h 10.04619"
+             id="path7551"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7553"
+             d="m 770.68188,164.82616 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7555"
+             d="m 780.35058,164.82616 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7557"
+             d="m 756.18726,177.12354 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,177.12354 h 5.76133"
+             id="path7559"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,177.12354 h 4.17599"
+             id="path7561"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,174.66406 h 10.04619"
+             id="path7563"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7565"
+             d="m 770.68188,174.66406 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7567"
+             d="m 780.35058,174.66406 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7569"
+             d="m 756.18726,172.20459 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,172.20459 h 5.76133"
+             id="path7571"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,172.20459 h 4.17599"
+             id="path7573"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,169.74511 h 10.04619"
+             id="path7575"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7577"
+             d="m 770.68188,169.74511 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7579"
+             d="m 780.35058,169.74511 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7581"
+             d="m 756.18726,167.28564 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,167.28564 h 5.76133"
+             id="path7583"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,167.28564 h 4.17599"
+             id="path7585"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,179.58301 h 10.04619"
+             id="path7587"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7589"
+             d="m 770.68188,179.58301 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7591"
+             d="m 780.35058,179.58301 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7593"
+             d="m 756.18726,182.04249 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,182.04249 h 5.76133"
+             id="path7595"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,182.04249 h 4.17599"
+             id="path7597"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,184.50197 h 10.04619"
+             id="path7599"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7601"
+             d="m 770.68188,184.50197 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7603"
+             d="m 780.35058,184.50197 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        </g>
+      </g>
+      <g
+         id="g3504"
+         transform="matrix(0.75,0,0,0.75,147.45632,111.09041)">
+        <circle
+           r="20.162695"
+           cy="73.545349"
+           cx="-173.12106"
+           transform="matrix(0.75,0,0,0.75,206.76781,428.32095)"
+           id="path3502"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#cccccc;stroke-width:1.20689654;marker:none;enable-background:accumulate" />
+        <g
+           inkscape:label="Layer 1"
+           id="layer1-6-3"
+           transform="matrix(0.77259294,0,0,0.77259294,-198.61013,38.289384)">
+          <g
+             id="g4180-7-6"
+             style="fill:#000000;fill-rule:evenodd;stroke:none"
+             transform="matrix(0.56420713,0,0,0.56420713,339.23445,559.72193)">
+            <g
+               id="g4182-5-7"
+               transform="translate(-986,-742)"
+               style="fill:#000000">
+              <g
+                 style="fill:#000000"
+                 id="g4184-3-8"
+                 transform="translate(986,742)">
+                <path
+                   style="fill:#000000"
+                   id="path4188-5-5"
+                   d="M 30,50 C 18.972,50 10,41.028 10,30 c 0,-0.33 0.009,-0.664 0.025,-1 0.185,-0.001 0.359,-0.036 0.538,-0.057 0.151,-0.017 0.307,-0.018 0.454,-0.048 0.177,-0.037 0.34,-0.104 0.51,-0.159 0.142,-0.046 0.29,-0.076 0.425,-0.134 0.16,-0.068 0.304,-0.163 0.455,-0.247 0.131,-0.073 0.27,-0.132 0.393,-0.215 0.14,-0.095 0.26,-0.214 0.39,-0.322 0.116,-0.097 0.241,-0.182 0.348,-0.289 0.12,-0.121 0.218,-0.262 0.325,-0.394 0.094,-0.116 0.2,-0.222 0.284,-0.346 0.1,-0.147 0.172,-0.312 0.255,-0.469 0.068,-0.127 0.149,-0.246 0.205,-0.379 0.08,-0.188 0.128,-0.391 0.184,-0.59 0.033,-0.117 0.083,-0.226 0.107,-0.346 C 14.965,24.68 15,24.344 15,24 15,23.702 14.964,23.411 14.911,23.124 14.898,23.047 14.884,22.97 14.867,22.893 14.808,22.64 14.734,22.393 14.637,22.154 14.596,22.053 14.547,21.958 14.5,21.86 14.429,21.712 14.351,21.568 14.265,21.428 14.191,21.305 14.116,21.185 14.032,21.07 13.914,20.908 13.786,20.753 13.649,20.606 13.555,20.505 13.457,20.411 13.355,20.319 13.252,20.224 13.143,20.137 13.031,20.052 12.945,19.985 12.869,19.908 12.778,19.847 16.374,13.748 22.883,10 30,10 41.028,10 50,18.972 50,30 50,41.028 41.028,50 30,50 M 52,30 C 52,17.869 42.131,8 30,8 22.072,8 14.841,12.234 10.918,19.093 10.62,19.037 10.315,19 10,19 7.243,19 5,21.243 5,24 5,26.073 6.269,27.854 8.07,28.611 8.041,29.076 8,29.544 8,30 8,42.131 17.869,52 30,52 42.131,52 52,42.131 52,30"
+                   inkscape:connector-curvature="0" />
+                <path
+                   style="fill:#000000"
+                   id="path4190-6-3"
+                   d="m 43.2881,54.6436 c -4.071,2.198 -8.646,3.356 -13.288,3.356 -15.44,0 -28,-12.56 -28,-28 0,-15.439 12.56,-28 28,-28 5.296,0 10.454,1.492 14.922,4.314 l 0.26,0.247 c -0.115,0.462 -0.182,0.943 -0.182,1.439 0,3.309 2.691,6 6,6 0.263,0 0.521,-0.022 0.775,-0.055 0.069,-0.009 0.136,-0.02 0.204,-0.032 0.219,-0.036 0.434,-0.083 0.645,-0.142 0.029,-0.008 0.06,-0.011 0.089,-0.02 l 0.255,0.244 c 3.292,4.714 5.032,10.247 5.032,16.005 0,7.001 -2.574,13.645 -7.269,18.811 -0.235,-0.154 -0.484,-0.28 -0.74,-0.392 -0.053,-0.022 -0.107,-0.039 -0.161,-0.06 -0.225,-0.089 -0.456,-0.161 -0.693,-0.216 -0.077,-0.018 -0.152,-0.036 -0.23,-0.051 -0.297,-0.054 -0.599,-0.092 -0.907,-0.092 -2.757,0 -5,2.244 -5,5 0,0.303 0.034,0.603 0.091,0.901 0.03,0.166 0.082,0.323 0.128,0.483 0.026,0.086 0.039,0.175 0.069,0.26 m 16.712,-24.644 c 0,-6.187 -1.877,-12.131 -5.424,-17.193 1.468,-1.095 2.424,-2.839 2.424,-4.807 0,-3.308 -2.691,-6 -6,-6 -2.068,0 -3.894,1.052 -4.974,2.648 -4.797,-3.04 -10.337,-4.648 -16.026,-4.648 -16.542,0 -30,13.458 -30,30 0,16.542 13.458,30 30,30 5.005,0 9.929,-1.27 14.311,-3.654 0.915,1.009 2.223,1.654 3.689,1.654 2.757,0 5,-2.243 5,-5 0,-1.031 -0.315,-1.991 -0.852,-2.788 5.066,-5.542 7.852,-12.682 7.852,-20.212"
+                   inkscape:connector-curvature="0" />
+              </g>
+            </g>
+          </g>
+          <path
+             inkscape:connector-curvature="0"
+             style="fill:#000000"
+             d="m 363.07286,583.10964 c 1.64873,-1.64873 1.64875,-4.31724 0,-5.96599 l -6.71633,-6.71632 c -0.99716,-0.99717 -2.36772,-1.38806 -3.66033,-1.17905 -0.0432,0.006 -0.0857,0.0112 -0.12771,0.0182 -0.0117,0.002 -0.0248,0.007 -0.0365,0.009 -0.65273,0.13194 -1.27922,0.41721 -1.82216,0.85976 -0.11095,0.0899 -0.21765,0.18574 -0.32156,0.28965 -1.64758,1.64758 -1.64875,4.32183 -2e-5,5.97056 l 6.71633,6.71633 c 1.64987,1.64761 4.3207,1.64528 5.96828,-0.002 z m -0.49943,-0.49943 c -1.36967,1.36967 -3.59744,1.36969 -4.96713,0 l -3.35932,-3.35932 4.96711,-4.96711 3.35932,3.35932 c 1.36969,1.36969 1.36967,3.59746 2e-5,4.96711 z m -10.48385,-6.65698 c -0.10043,0.13076 -0.30148,0.13602 -0.42876,0.0227 -0.0163,-0.014 -1.81286,-1.66965 -0.8963,-3.94311 0.014,-0.0397 0.0382,-0.0746 0.0662,-0.10262 0.0829,-0.0829 0.21392,-0.11514 0.32837,-0.0684 0.15764,0.0642 0.23183,0.24037 0.16876,0.39683 -0.76014,1.88344 0.72485,3.24948 0.74124,3.26354 0.12489,0.11322 0.13375,0.30605 0.0205,0.43104 z"
+             id="path4220-2-4" />
+          <ellipse
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.00000024;marker:none;enable-background:accumulate"
+             id="path4248-7"
+             cx="344.8399"
+             cy="573.29431"
+             rx="2.3967094"
+             ry="2.3335748" />
+          <circle
+             r="2.3335748"
+             cy="589.61456"
+             cx="366.30563"
+             id="circle4250-5"
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.00000024;marker:none;enable-background:accumulate" />
+          <circle
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.00000024;marker:none;enable-background:accumulate"
+             id="circle4252-6"
+             cx="367.95255"
+             cy="564.29224"
+             r="2.9017856" />
+        </g>
+      </g>
+    </g>
+    <g
+       id="g5400"
+       transform="matrix(0.46564321,0,0,0.46534938,368.59824,407.59081)"
+       style="stroke-width:1.47446001" />
+    <text
+       xml:space="preserve"
+       id="text1362"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect1364);fill:#000000;fill-opacity:1;stroke:none"
+       x="101.8434"
+       y="233.31212" />
+    <text
+       xml:space="preserve"
+       id="text8092"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect8094);fill:#000000;fill-opacity:1;stroke:none" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
+       x="510.64374"
+       y="398.73453"
+       id="text8348"><tspan
+         sodipodi:role="line"
+         id="tspan8346"
+         x="510.64374"
+         y="434.12515" /></text>
+    <text
+       id="text8012"
+       y="482.03729"
+       x="566.41211"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
+       xml:space="preserve"><tspan
+         y="517.42792"
+         x="566.41211"
+         id="tspan8010"
+         sodipodi:role="line" /></text>
+    <text
+       id="text8034"
+       y="584.48798"
+       x="582.87738"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
+       xml:space="preserve"><tspan
+         y="619.8786"
+         x="582.87738"
+         id="tspan8032"
+         sodipodi:role="line" /></text>
+    <g
+       style="fill-rule:evenodd"
+       id="g3126"
+       transform="matrix(-0.31969042,0.55372006,0.55372006,0.31969042,270.40661,491.71769)">
+      <g
+         transform="matrix(0,0.46327147,-0.46327147,0,349.99468,70.425726)"
+         id="g3124"
+         style="fill:#000000;fill-rule:evenodd">
+        <g
+           style="fill:#000000"
+           id="g3122"
+           transform="translate(-273.13876,667.95829)">
+          <path
+             sodipodi:nodetypes="cccccccccc"
+             inkscape:connector-curvature="0"
+             id="path3120"
+             transform="matrix(2.1585616,0,0,2.1585616,-155.44878,-106.41793)"
+             d="m 93.558594,52.710938 v 5.22559 H 81.091797 v 4.841788 h 12.466797 v 5.227543 l 6.623046,-3.824218 6.62109,-3.824219 -6.62109,-3.822266 z"
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;enable-background:accumulate" />
+        </g>
+      </g>
+    </g>
+    <rect
+       transform="rotate(90)"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffcc00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.58393043;marker:none;enable-background:accumulate"
+       id="rect5126"
+       width="12.880835"
+       height="4.046061"
+       x="464.03442"
+       y="-265.56158" />
+    <rect
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#000000;stroke-width:0.78556401;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+       id="rect5138"
+       width="17.185421"
+       height="12.415584"
+       x="249.37277"
+       y="464.27072" />
+    <text
+       id="text5140"
+       y="469.75943"
+       x="275.21021"
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.60573244px;line-height:0%;font-family:monospace;-inkscape-font-specification:Monospace;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.46714437px;marker:none;enable-background:accumulate"
+       xml:space="preserve"><tspan
+         y="469.75943"
+         x="275.21021"
+         id="tspan5142"
+         sodipodi:role="line"
+         style="font-size:4.20429945px;line-height:1.25;font-family:monospace;text-align:center;text-anchor:middle;stroke-width:0.46714437px">final</tspan><tspan
+         y="475.0148"
+         x="275.21021"
+         sodipodi:role="line"
+         style="font-size:4.20429945px;line-height:1.25;font-family:monospace;text-align:center;text-anchor:middle;stroke-width:0.46714437px"
+         id="tspan3229">csv</tspan><tspan
+         y="478.00653"
+         x="275.21021"
+         sodipodi:role="line"
+         style="font-size:4.20429945px;line-height:1.25;font-family:monospace;text-align:center;text-anchor:middle;stroke-width:0.46714437px"
+         id="tspan3227" /></text>
+    <path
+       inkscape:connector-curvature="0"
+       id="path5168"
+       d="m 251.28213,465.7828 h 4.69302"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 258.05321,465.7828 h 2.69137"
+       id="path5170"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 262.56989,465.7828 h 1.95079"
+       id="path5172"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 251.28213,471.52745 h 4.69302"
+       id="path5174"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5176"
+       d="m 258.05321,471.52745 h 2.69137"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5178"
+       d="m 262.56989,471.52745 h 1.95079"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5180"
+       d="m 251.28213,470.37852 h 4.69302"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 258.05321,470.37852 h 2.69137"
+       id="path5182"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 262.56989,470.37852 h 1.95079"
+       id="path5184"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 251.28213,469.22959 h 4.69302"
+       id="path5186"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5188"
+       d="m 258.05321,469.22959 h 2.69137"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5190"
+       d="m 262.56989,469.22959 h 1.95079"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5192"
+       d="m 251.28213,468.08066 h 4.69302"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 258.05321,468.08066 h 2.69137"
+       id="path5194"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 262.56989,468.08066 h 1.95079"
+       id="path5196"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 251.28213,466.93173 h 4.69302"
+       id="path5198"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5200"
+       d="m 258.05321,466.93173 h 2.69137"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5202"
+       d="m 262.56989,466.93173 h 1.95079"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5204"
+       d="m 251.28213,472.67638 h 4.69302"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 258.05321,472.67638 h 2.69137"
+       id="path5206"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 262.56989,472.67638 h 1.95079"
+       id="path5208"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 251.28213,473.82531 h 4.69302"
+       id="path5210"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5212"
+       d="m 258.05321,473.82531 h 2.69137"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5214"
+       d="m 262.56989,473.82531 h 1.95079"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5216"
+       d="m 251.28213,474.97424 h 4.69302"
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 258.05321,474.97424 h 2.69137"
+       id="path5218"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#4d4d4d;stroke:#666666;stroke-width:0.57446611px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 262.56989,474.97424 h 1.95079"
+       id="path5220"
+       inkscape:connector-curvature="0" />
+    <g
+       id="g1245"
+       transform="translate(-8.614681,-9.220223)">
+      <rect
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#000000;stroke-width:0.80361438;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+         id="rect6963"
+         width="16.905058"
+         height="12.274723"
+         x="-517.39722"
+         y="261.39746"
+         transform="rotate(-90)" />
+      <text
+         transform="scale(1.0025223,0.99748404)"
+         id="text6965"
+         y="509.42337"
+         x="282.10519"
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.52818871px;line-height:0%;font-family:monospace;-inkscape-font-specification:Monospace;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:middle;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35831323px;marker:none;enable-background:accumulate"
+         xml:space="preserve"><tspan
+           y="509.42337"
+           x="282.10519"
+           id="tspan6967"
+           sodipodi:role="line"
+           style="font-size:4.14614153px;line-height:1.25;font-family:monospace;text-align:center;text-anchor:middle;stroke-width:0.35831323px">sample</tspan><tspan
+           y="514.60602"
+           x="282.10519"
+           sodipodi:role="line"
+           style="font-size:4.14614153px;line-height:1.25;font-family:monospace;text-align:center;text-anchor:middle;stroke-width:0.35831323px"
+           id="tspan3300">yaml</tspan></text>
+      <path
+         inkscape:connector-curvature="0"
+         id="path6969"
+         d="m 262.67925,502.0913 h 4.63978"
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 264.38309,503.22149 h 4.63978"
+         id="path6971"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6973"
+         d="m 264.38309,504.35168 h 4.63978"
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 262.67925,505.48186 h 4.63978"
+         id="path6975"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6977"
+         d="m 264.38309,506.61205 h 4.63978"
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 262.67925,507.74224 h 4.63978"
+         id="path6979"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6981"
+         d="m 264.38309,508.87242 h 4.63978"
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 265.51899,510.00261 h 4.63977"
+         id="path6983"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6985"
+         d="m 262.67925,513.39317 h 4.63978"
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 263.81515,514.52335 h 4.63977"
+         id="path6987"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6989"
+         d="m 265.51899,511.1328 h 4.63977"
+         style="fill:#4d4d4d;stroke:#666666;stroke-width:0.5876649px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.3961668px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.41214061;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       x="234.76807"
+       y="483.89142"
+       id="text1156"><tspan
+         sodipodi:role="line"
+         id="tspan1154"
+         x="234.76807"
+         y="483.89142"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.03442717px;line-height:0;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.41214061">eido</tspan><tspan
+         sodipodi:role="line"
+         x="234.76807"
+         y="489.38663"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.03442717px;line-height:0;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.41214061"
+         id="tspan1158">convert</tspan></text>
+  </g>
+</svg>
diff --git a/docs/img/eido.svg b/docs/img/eido.svg
index dc7873db..de00ec82 100644
--- a/docs/img/eido.svg
+++ b/docs/img/eido.svg
@@ -15,7 +15,7 @@
    viewBox="0 0 66.525213 24.766375"
    id="svg6"
    sodipodi:docname="eido.svg"
-   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
    style="fill:#000000">
   <metadata
      id="metadata12">
@@ -25,7 +25,7 @@
         <dc:format>image/svg+xml</dc:format>
         <dc:type
            rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
+        <dc:title />
       </cc:Work>
     </rdf:RDF>
   </metadata>
@@ -40,22 +40,29 @@
      guidetolerance="10"
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
-     inkscape:window-width="1920"
-     inkscape:window-height="1139"
+     inkscape:window-width="2488"
+     inkscape:window-height="1343"
      id="namedview8"
      showgrid="false"
      inkscape:zoom="5.9962655"
-     inkscape:cx="49.397377"
-     inkscape:cy="-1.8650012"
+     inkscape:cx="18.461455"
+     inkscape:cy="-2.5320831"
      inkscape:current-layer="svg6"
      fit-margin-top="2"
      fit-margin-left="2"
      fit-margin-right="2"
      fit-margin-bottom="2"
      inkscape:snap-global="false"
-     inkscape:window-x="0"
-     inkscape:window-y="0"
+     inkscape:window-x="1272"
+     inkscape:window-y="231"
      inkscape:window-maximized="1" />
+  <rect
+     style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke"
+     id="rect829"
+     width="68.876205"
+     height="27.350357"
+     x="-0.50031137"
+     y="-0.41596597" />
   <g
      id="g1336"
      style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.66666698px;line-height:1;font-family:Arial;-inkscape-font-specification:Arial;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000001;fill-opacity:1;stroke:none;stroke-width:1.06699991;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
diff --git a/webeido/static/eido_light.svg b/docs/img/eido_light.svg
similarity index 100%
rename from webeido/static/eido_light.svg
rename to docs/img/eido_light.svg
diff --git a/webeido/static/no.png b/docs/img/no.png
similarity index 100%
rename from webeido/static/no.png
rename to docs/img/no.png
diff --git a/webeido/static/style.css b/docs/img/style.css
similarity index 100%
rename from webeido/static/style.css
rename to docs/img/style.css
diff --git a/docs/img/validation.svg b/docs/img/validation.svg
new file mode 100644
index 00000000..ad7681eb
--- /dev/null
+++ b/docs/img/validation.svg
@@ -0,0 +1,1845 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   sodipodi:docname="validation.svg"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   version="1.1"
+   id="svg2"
+   height="62.119236"
+   width="98.571861">
+  <defs
+     id="defs4">
+    <rect
+       id="rect8094"
+       height="300.37643"
+       width="267.2688"
+       y="542.2005"
+       x="473.57208" />
+    <marker
+       style="overflow:visible"
+       id="TriangleOutM"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="TriangleOutM">
+      <path
+         inkscape:connector-curvature="0"
+         transform="scale(0.4)"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt"
+         d="M 5.77,0 -2.88,5 V -5 Z"
+         id="path4156" />
+    </marker>
+    <linearGradient
+       id="linearGradient892-3-0">
+      <stop
+         id="stop893-2-3"
+         offset="0.00000000"
+         style="stop-color:#ffffff;stop-opacity:0.00000000;" />
+      <stop
+         id="stop894-2-1"
+         offset="1"
+         style="stop-color:#fff;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4492"
+       y2="89"
+       gradientUnits="userSpaceOnUse"
+       y1="20"
+       x2="100"
+       x1="35">
+      <stop
+         id="stop4488"
+         stop-color="#00c0c0"
+         stop-opacity=".75294"
+         offset="0" />
+      <stop
+         id="stop4490"
+         stop-color="#80f5f5"
+         stop-opacity=".30196"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3161">
+      <stop
+         id="stop3163"
+         stop-color="#fff"
+         offset="0" />
+      <stop
+         id="stop3165"
+         stop-color="#fff"
+         stop-opacity="0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="108.87218"
+       y1="108.87218"
+       xlink:href="#linearGradient15251"
+       x2="292.65842"
+       x1="285.47037"
+       id="linearGradient15378"
+       gradientTransform="matrix(0.89839544,0,0,1.4262987,-2583.0381,2966.5502)" />
+    <linearGradient
+       id="linearGradient15251">
+      <stop
+         stop-color="#11ffff"
+         offset="0"
+         id="stop15252" />
+      <stop
+         stop-color="#e1ebff"
+         stop-opacity="0"
+         offset="1"
+         id="stop15253" />
+    </linearGradient>
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="186.81477"
+       y1="186.81477"
+       xlink:href="#linearGradient15251"
+       x2="244.25108"
+       x1="239.22408"
+       id="linearGradient15379"
+       gradientTransform="matrix(1.2750153,0,0,1.0049921,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="111.47942"
+       y1="111.47942"
+       xlink:href="#linearGradient15251"
+       x2="311.97397"
+       x1="305.36746"
+       id="linearGradient15380"
+       gradientTransform="matrix(1.148607,0,0,1.115595,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="270.58075"
+       y1="270.58075"
+       xlink:href="#linearGradient15251"
+       x2="271.39532"
+       x1="266.74103"
+       id="linearGradient15383"
+       gradientTransform="matrix(1.5031099,0,0,0.85248603,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="212.56871"
+       y1="212.56871"
+       xlink:href="#linearGradient15251"
+       x2="175.87711"
+       x1="169.62802"
+       id="linearGradient15384"
+       gradientTransform="matrix(1.149307,0,0,1.1149155,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="220.00958"
+       y1="220.00958"
+       xlink:href="#linearGradient15251"
+       x2="215.18179"
+       x1="208.20387"
+       id="linearGradient15385"
+       gradientTransform="matrix(0.9480899,0,0,1.3515387,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="264.51785"
+       y1="264.51785"
+       xlink:href="#linearGradient15251"
+       x2="219.02859"
+       x1="216.48724"
+       id="linearGradient15386"
+       gradientTransform="matrix(1.6080623,0,0,0.79684736,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="529.10437"
+       y1="529.10437"
+       xlink:href="#linearGradient15251"
+       x2="186.3316"
+       x1="184.40688"
+       id="linearGradient15387"
+       gradientTransform="matrix(1.847117,0,0,0.69371905,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="245.84831"
+       y1="245.84831"
+       xlink:href="#linearGradient15251"
+       x2="315.26337"
+       x1="307.05576"
+       id="linearGradient15388"
+       gradientTransform="matrix(0.92408916,0,0,1.3866413,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="168.36928"
+       y1="168.36928"
+       xlink:href="#linearGradient15251"
+       x2="630.80371"
+       x1="622.69678"
+       id="linearGradient15389"
+       gradientTransform="matrix(0.72507885,0,0,1.7672288,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="234.81921"
+       y1="234.81921"
+       xlink:href="#linearGradient15251"
+       x2="495.96729"
+       x1="491.17938"
+       id="linearGradient15390"
+       gradientTransform="matrix(0.93320216,0,0,1.3731004,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="225.18878"
+       y1="225.18878"
+       xlink:href="#linearGradient15251"
+       x2="535.78436"
+       x1="527.91168"
+       id="linearGradient15391"
+       gradientTransform="matrix(0.97200857,0,0,1.3182808,-2583.0381,2966.5502)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="238.44827"
+       y1="238.44827"
+       xlink:href="#linearGradient15251"
+       x2="206.7001"
+       x1="204.62263"
+       id="linearGradient15392"
+       gradientTransform="matrix(1.2300616,0,0,1.0417204,-2583.0381,2966.5502)" />
+    <linearGradient
+       id="E">
+      <stop
+         id="R"
+         stop-color="#2e3436" />
+      <stop
+         id="S"
+         offset="1"
+         stop-color="#2e3436"
+         stop-opacity="0" />
+    </linearGradient>
+    <linearGradient
+       id="D">
+      <stop
+         id="P"
+         stop-color="#eeeeec" />
+      <stop
+         id="Q"
+         offset="1"
+         stop-color="#d3d7cf" />
+    </linearGradient>
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-259.90161,564.66242)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-129.81603,374.91355)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-249.83623,466.9785)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-332.79973,444.08618)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-249.83623,466.61717)"
+       x1="34.130001"
+       y1="8.75"
+       x2="36.533001"
+       y2="6.3629999"
+       id="J"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-281.84156,543.48915)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-151.75598,353.74028)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-271.77618,445.80523)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-354.73969,422.91291)" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0-3"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0-5" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5-6"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(3.2895963,0,0,0.26843569,123.83968,345.41502)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3-2"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,284.67246,110.81677)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,136.28408,224.64237)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7-9"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4619"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4621" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9-1"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,33.711276,196.33917)" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-2"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-7" />
+    </filter>
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-0"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,496.7991,197.14557)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.9711)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-9"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4688"
+       style="color-interpolation-filters:sRGB">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4690" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-3"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,245.83788,282.66792)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.52437)"
+       x1="34.130001"
+       y1="8.75"
+       x2="36.533001"
+       y2="6.3629999"
+       id="J-6"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="26.648937"
+       y1="20.603781"
+       x2="135.66525"
+       y2="114.39767"
+       id="linearGradient1478"
+       xlink:href="#linearGradient4689"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" />
+    <linearGradient
+       id="linearGradient4689">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop4691" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop4693" />
+    </linearGradient>
+    <linearGradient
+       x1="150.96111"
+       y1="192.35176"
+       x2="112.03144"
+       y2="137.27299"
+       id="linearGradient1475"
+       xlink:href="#linearGradient4671"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" />
+    <linearGradient
+       id="linearGradient4671">
+      <stop
+         style="stop-color:#ffd43b;stop-opacity:1"
+         offset="0"
+         id="stop4673" />
+      <stop
+         style="stop-color:#ffe873;stop-opacity:1"
+         offset="1"
+         id="stop4675" />
+    </linearGradient>
+    <radialGradient
+       cx="61.518883"
+       cy="132.28575"
+       r="29.036913"
+       fx="61.518883"
+       fy="132.28575"
+       id="radialGradient1480"
+       xlink:href="#linearGradient2795"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.382716e-8,-0.296405,1.43676,4.683673e-7,-128.544,150.5202)" />
+    <linearGradient
+       id="linearGradient2795">
+      <stop
+         style="stop-color:#b8b8b8;stop-opacity:0.49803922"
+         offset="0"
+         id="stop2797" />
+      <stop
+         style="stop-color:#7f7f7f;stop-opacity:0"
+         offset="1"
+         id="stop2799" />
+    </linearGradient>
+    <linearGradient
+       id="gradientFill-1"
+       x1="0.7414425"
+       x2="590.86261"
+       y1="3.6658268"
+       y2="593.78699"
+       gradientUnits="userSpaceOnUse"
+       spreadMethod="pad"
+       gradientTransform="matrix(0.11494196,0,0,0.07697987,293.05555,752.18406)">
+      <stop
+         offset="0"
+         stop-color="rgb(203,206,208)"
+         stop-opacity="1"
+         id="stop7" />
+      <stop
+         offset="1"
+         stop-color="rgb(132,131,139)"
+         stop-opacity="1"
+         id="stop9" />
+    </linearGradient>
+    <linearGradient
+       id="gradientFill-2"
+       x1="301.02603"
+       x2="703.0675"
+       y1="151.4006"
+       y2="553.44208"
+       gradientUnits="userSpaceOnUse"
+       spreadMethod="pad"
+       gradientTransform="matrix(0.09311939,0,0,0.09502015,293.05555,752.18406)">
+      <stop
+         offset="0"
+         stop-color="rgb(39,109,195)"
+         stop-opacity="1"
+         id="stop12" />
+      <stop
+         offset="1"
+         stop-color="rgb(22,92,170)"
+         stop-opacity="1"
+         id="stop14" />
+    </linearGradient>
+    <circle
+       id="f"
+       cx="16"
+       cy="16"
+       r="16" />
+    <polygon
+       id="d"
+       points="3.918,0 21.394,0 28.082,6.689 28.082,32 3.918,32 " />
+    <path
+       inkscape:connector-curvature="0"
+       id="b"
+       d="M 26.082,32 H 5.917 c -1.1,0 -2,-0.9 -2,-2 V 2 c 0,-1.1 0.9,-2 2,-2 h 20.165 c 1.1,0 2,0.9 2,2 v 28 c 0,1.1 -0.9,2 -2,2 z" />
+    <style
+       type="text/css"
+       id="style6">
+   
+    .fil1 {fill:none}
+    .fil0 {fill:black}
+   
+  </style>
+    <rect
+       id="rect1364"
+       height="248.14047"
+       width="547.18158"
+       y="295.46783"
+       x="434.12497" />
+    <marker
+       style="overflow:visible"
+       id="TriangleOutM-4"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="TriangleOutM">
+      <path
+         inkscape:connector-curvature="0"
+         transform="scale(0.4)"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt"
+         d="M 5.77,0 -2.88,5 V -5 Z"
+         id="path4156-9" />
+    </marker>
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-9">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-6" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-0"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-259.90161,564.66242)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-2"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-129.81603,374.91355)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-249.83623,466.9785)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-8"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-4"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-332.79973,444.08618)" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0-4">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0-8" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5-0"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.6607075,0,0,0.21711748,-281.84156,543.48915)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3-1"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.5010936,6.2249964,-9.2108788,2.221111,-151.75598,353.74028)" />
+    <linearGradient
+       gradientTransform="matrix(2.4311654,0,0,2.4311654,-271.77618,445.80523)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7-5"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9-11"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.8623306,5.0367582e-7,-5.2704226e-7,5.0879031,-354.73969,422.91291)" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-0-3-1">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-0-5-5" />
+    </filter>
+    <radialGradient
+       cx="25.712"
+       cy="48.735001"
+       r="21.856001"
+       id="F-5-6-1"
+       xlink:href="#E"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(3.2895963,0,0,0.26843569,123.83968,345.41502)" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-3-2-7"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,284.67246,110.81677)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,136.28408,224.64237)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-7-9-0"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4619-8">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4621-5" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-9-1-3"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,33.711276,196.33917)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#D"
+       id="radialGradient5032"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,848.22767,302.28895)"
+       cx="13.559"
+       cy="12.06"
+       r="16.219" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient892-3-0"
+       id="linearGradient5034"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,699.83932,416.11451)"
+       y1="2.438"
+       x2="0"
+       y2="43.34" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="K-2-7">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="T-7-9" />
+    </filter>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#D"
+       id="radialGradient5036"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,597.26643,387.81133)"
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03" />
+    <radialGradient
+       cx="13.559"
+       cy="12.06"
+       r="16.219"
+       id="G-0-6"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,496.7991,197.14557)" />
+    <linearGradient
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.9711)"
+       y1="2.438"
+       x2="0"
+       y2="43.34"
+       id="H-9-3"
+       xlink:href="#linearGradient892-3-0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       style="color-interpolation-filters:sRGB"
+       x="-0.16"
+       y="-0.15099999"
+       width="1.321"
+       height="1.302"
+       id="filter4688-1">
+      <feGaussianBlur
+         stdDeviation="0.5327"
+         id="feGaussianBlur4690-5" />
+    </filter>
+    <radialGradient
+       cx="34.130001"
+       cy="8.6090002"
+       r="3.03"
+       id="I-3-8"
+       xlink:href="#D"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,245.83788,282.66792)" />
+    <radialGradient
+       cx="61.518883"
+       cy="132.28575"
+       r="29.036913"
+       fx="61.518883"
+       fy="132.28575"
+       id="radialGradient1480-3"
+       xlink:href="#linearGradient2795"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.382716e-8,-0.296405,1.43676,4.683673e-7,-128.544,150.5202)" />
+    <radialGradient
+       r="16.219"
+       cy="12.06"
+       cx="13.559"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,848.22767,302.28895)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6720"
+       xlink:href="#D"
+       inkscape:collect="always" />
+    <linearGradient
+       y2="43.34"
+       x2="0"
+       y1="2.438"
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,699.83932,416.11451)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient6722"
+       xlink:href="#linearGradient892-3-0"
+       inkscape:collect="always" />
+    <radialGradient
+       r="3.03"
+       cy="8.6090002"
+       cx="34.130001"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,597.26643,387.81133)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6724"
+       xlink:href="#D"
+       inkscape:collect="always" />
+    <radialGradient
+       r="16.219"
+       cy="12.06"
+       cx="13.559"
+       gradientTransform="matrix(1.8558945,7.6963458,-11.387976,2.746096,496.7991,197.14557)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6730"
+       xlink:href="#D"
+       inkscape:collect="always" />
+    <linearGradient
+       y2="43.34"
+       x2="0"
+       y1="2.438"
+       gradientTransform="matrix(3.0057991,0,0,3.0057991,348.41075,310.9711)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient6732"
+       xlink:href="#linearGradient892-3-0"
+       inkscape:collect="always" />
+    <radialGradient
+       r="3.03"
+       cy="8.6090002"
+       cx="34.130001"
+       gradientTransform="matrix(6.0115982,6.2272537e-7,-6.5161475e-7,6.2904874,245.83788,282.66792)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6734"
+       xlink:href="#D"
+       inkscape:collect="always" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:document-rotation="0"
+     inkscape:snap-global="false"
+     units="mm"
+     fit-margin-bottom="2"
+     fit-margin-right="2"
+     fit-margin-left="2"
+     fit-margin-top="2"
+     inkscape:window-maximized="1"
+     inkscape:window-y="231"
+     inkscape:window-x="1272"
+     inkscape:window-height="1343"
+     inkscape:window-width="2488"
+     showgrid="false"
+     inkscape:current-layer="layer1"
+     inkscape:document-units="px"
+     inkscape:cy="8.1590864"
+     inkscape:cx="8.185875"
+     inkscape:zoom="6.507435"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="1"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     transform="translate(-191.2375,-442.13937)"
+     id="layer1"
+     inkscape:groupmode="layer"
+     inkscape:label="Layer 1">
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1330"
+       display="none"
+       style="display:none">
+      <g
+         id="g1326"
+         display="inline"
+         style="display:inline">
+        <polygon
+           id="polygon1316"
+           points="29.245,6.689 29.245,32 5.08,32 5.08,0 22.556,0 "
+           style="fill:#000000" />
+        <polygon
+           id="polygon1318"
+           points="29.245,6.689 22.555,6.689 22.555,0 "
+           style="fill:#000000" />
+        <g
+           id="g1324">
+          <rect
+             id="rect1320"
+             height="8.118"
+             width="24.093"
+             y="18.313"
+             x="2.7550001"
+             style="fill:#000000" />
+          <polygon
+             id="polygon1322"
+             points="5.08,28.755 5.08,26.431 2.755,26.431 "
+             style="fill:#000000" />
+        </g>
+      </g>
+      <text
+         id="text1328"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(4.9424,24.8354)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline;fill:#000000">HTML</text>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1334"
+       display="none"
+       style="display:none">
+      <text
+         id="text1332"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(5.7757,24.8354)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline;fill:#000000">HTML</text>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1350"
+       display="none"
+       style="display:none">
+      <g
+         id="g1346"
+         display="inline"
+         style="display:inline">
+        <polygon
+           id="polygon1336"
+           points="29.245,6.689 29.245,32 5.08,32 5.08,0 22.556,0 "
+           style="fill:#000000" />
+        <polygon
+           id="polygon1338"
+           points="29.245,6.689 22.555,6.689 22.555,0 "
+           style="fill:#000000" />
+        <g
+           id="g1344">
+          <polygon
+             id="polygon1340"
+             points="2.755,26.431 2.755,18.313 32,18.313 26.848,26.431 "
+             style="fill:#000000" />
+          <polygon
+             id="polygon1342"
+             points="5.08,28.755 5.08,26.431 2.755,26.431 "
+             style="fill:#000000" />
+        </g>
+      </g>
+      <text
+         id="text1348"
+         font-size="7px"
+         display="inline"
+         transform="translate(6.3452,24.7163)"
+         style="font-size:7px;line-height:0%;font-family:EurostileBold;display:inline;fill:#000000">HTML</text>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1382"
+       display="none"
+       style="display:none">
+      <g
+         id="g1364"
+         display="inline"
+         style="display:inline">
+        <circle
+           id="circle1352"
+           r="16"
+           cy="16"
+           cx="16"
+           style="fill:#000000" />
+        <g
+           id="g1362">
+          <defs
+             id="defs1355">
+            <circle
+               r="16"
+               cy="16"
+               cx="16"
+               id="circle1566" />
+          </defs>
+          <clipPath
+             id="e">
+            <use
+               id="use1357"
+               overflow="visible"
+               xlink:href="#f"
+               style="overflow:visible"
+               x="0"
+               y="0"
+               width="616.53546"
+               height="212.59842" />
+          </clipPath>
+          <polygon
+             id="polygon1360"
+             points="33.884,20.648 33.884,32.886 16.216,33.948 8.268,26 23.417,10.181 "
+             clip-path="url(#e)"
+             style="fill:#000000" />
+        </g>
+      </g>
+      <g
+         id="g1380"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1376">
+          <polygon
+             id="polygon1366"
+             points="23.417,10.181 23.417,26 8.314,26 8.314,6 19.237,6 "
+             style="fill:#000000" />
+          <polygon
+             id="polygon1368"
+             points="23.417,10.181 19.236,10.181 19.236,6 "
+             style="fill:#000000" />
+          <g
+             id="g1374">
+            <polygon
+               id="polygon1370"
+               points="6.861,22.519 6.861,17.446 25.139,17.446 21.919,22.519 "
+               style="fill:#000000" />
+            <polygon
+               id="polygon1372"
+               points="8.314,23.972 8.314,22.519 6.861,22.519 "
+               style="fill:#000000" />
+          </g>
+        </g>
+        <text
+           id="text1378"
+           font-size="4.375px"
+           transform="translate(9.1048,21.4479)"
+           style="font-size:4.375px;line-height:0%;font-family:EurostileBold;fill:#000000">HTML</text>
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1422"
+       display="none"
+       style="display:none">
+      <g
+         id="g1420"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1408">
+          <g
+             id="g1394">
+            <g
+               id="g1386">
+              <path
+                 id="path1384"
+                 d="m 5.917,31.516 c -0.827,0 -1.5,-0.673 -1.5,-1.5 v -28 c 0,-0.827 0.673,-1.5 1.5,-1.5 h 20.165 c 0.827,0 1.5,0.673 1.5,1.5 v 28 c 0,0.827 -0.673,1.5 -1.5,1.5 z"
+                 inkscape:connector-curvature="0"
+                 style="fill:#000000" />
+            </g>
+            <g
+               id="g1392">
+              <g
+                 id="g1390">
+                <path
+                   id="path1388"
+                   d="m 26.082,1.016 c 0.551,0 1,0.449 1,1 v 28 c 0,0.551 -0.449,1 -1,1 H 5.918 c -0.551,0 -1,-0.449 -1,-1 v -28 c 0,-0.551 0.449,-1 1,-1 h 20.164 m 0,-1 H 5.918 c -1.1,0 -2,0.9 -2,2 v 28 c 0,1.1 0.9,2 2,2 h 20.165 c 1.1,0 2,-0.9 2,-2 v -28 c -10e-4,-1.1 -0.901,-2 -2.001,-2 z"
+                   inkscape:connector-curvature="0"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+          <g
+             id="g1402">
+            <g
+               id="g1400">
+              <g
+                 id="g1398">
+                <rect
+                   id="rect1396"
+                   height="8.118"
+                   width="24.165001"
+                   y="18.329"
+                   x="3.918"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+          <g
+             id="g1406">
+            <polyline
+               id="polyline1404"
+               points="3.918,26.408 3.918,18.329 11.996,18.329     "
+               style="fill:#000000" />
+          </g>
+        </g>
+        <g
+           id="g1418">
+          <path
+             id="path1410"
+             d="m 10.771,19.914 v 4.922 H 9.837 V 22.708 H 7.529 v 2.128 H 6.595 v -4.922 h 0.934 v 2.008 h 2.308 v -2.008 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1412"
+             d="m 13.599,20.75 v 4.086 H 12.665 V 20.75 h -1.421 v -0.836 h 3.826 v 0.836 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1414"
+             d="m 21.297,19.914 v 4.922 h -0.934 v -2.683 c 0,-0.214 0.006,-0.457 0.018,-0.729 l 0.018,-0.368 0.02,-0.364 H 20.39 l -0.111,0.343 -0.109,0.343 c -0.102,0.308 -0.18,0.535 -0.234,0.682 l -1.082,2.776 h -0.85 L 16.91,22.081 C 16.85,21.93 16.771,21.702 16.672,21.399 L 16.56,21.056 16.448,20.717 h -0.029 l 0.018,0.357 0.018,0.36 c 0.014,0.278 0.021,0.518 0.021,0.718 v 2.683 h -0.934 v -4.922 h 1.522 l 0.88,2.278 c 0.061,0.159 0.139,0.386 0.238,0.682 l 0.107,0.343 0.111,0.339 h 0.033 l 0.105,-0.339 0.107,-0.339 c 0.09,-0.286 0.166,-0.512 0.23,-0.678 l 0.865,-2.286 h 1.557 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1416"
+             d="m 23.097,19.914 v 4.085 h 2.308 v 0.837 h -3.242 v -4.922 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+        </g>
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1460"
+       display="none"
+       style="display:none">
+      <g
+         id="g1446"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1426">
+          <circle
+             id="circle1424"
+             r="16"
+             cy="16"
+             cx="16"
+             style="fill:#000000" />
+        </g>
+        <g
+           id="g1444">
+          <g
+             id="g1434">
+            <g
+               id="g1432">
+              <g
+                 id="g1430">
+                <path
+                   id="path1428"
+                   d="M 22.302,6 H 9.698 C 9.01,6 8.448,6.562 8.448,7.25 v 17.5 c 0,0.688 0.562,1.25 1.25,1.25 h 12.603 c 0.687,0 1.25,-0.562 1.25,-1.25 V 7.25 C 23.552,6.562 22.989,6 22.302,6 Z"
+                   inkscape:connector-curvature="0"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+          <g
+             id="g1442">
+            <g
+               id="g1440">
+              <g
+                 id="g1438">
+                <rect
+                   id="rect1436"
+                   height="5.0739999"
+                   width="15.103"
+                   y="17.445999"
+                   x="8.448"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+        </g>
+      </g>
+      <g
+         id="g1458"
+         display="inline"
+         style="display:inline">
+        <g
+           id="g1456">
+          <path
+             id="path1448"
+             d="m 12.857,18.436 v 3.076 h -0.583 v -1.33 h -1.443 v 1.33 h -0.584 v -3.076 h 0.584 v 1.255 h 1.443 v -1.255 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1450"
+             d="m 14.624,18.959 v 2.554 H 14.04 v -2.554 h -0.888 v -0.522 h 2.391 v 0.522 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1452"
+             d="m 19.436,18.436 v 3.076 h -0.583 v -1.677 c 0,-0.134 0.004,-0.286 0.011,-0.455 l 0.011,-0.23 0.012,-0.228 h -0.018 l -0.07,0.214 -0.068,0.214 c -0.063,0.192 -0.112,0.334 -0.146,0.426 l -0.676,1.735 h -0.531 l -0.684,-1.722 c -0.038,-0.095 -0.087,-0.237 -0.149,-0.426 l -0.07,-0.214 -0.07,-0.212 h -0.018 l 0.011,0.223 0.011,0.225 c 0.009,0.174 0.013,0.323 0.013,0.449 v 1.677 h -0.584 v -3.076 h 0.951 l 0.55,1.424 c 0.038,0.099 0.087,0.241 0.149,0.426 l 0.067,0.214 0.07,0.212 h 0.021 l 0.066,-0.212 0.067,-0.212 c 0.056,-0.179 0.104,-0.32 0.144,-0.424 l 0.541,-1.429 h 0.972 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+          <path
+             id="path1454"
+             d="m 20.561,18.436 v 2.553 h 1.442 v 0.523 h -2.026 v -3.076 z"
+             inkscape:connector-curvature="0"
+             style="fill:#000000" />
+        </g>
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1510"
+       display="none"
+       style="display:none">
+      <g
+         id="g1498"
+         display="inline"
+         style="display:inline">
+        <path
+           id="path1462"
+           d="M 26.082,32 H 5.917 c -1.1,0 -2,-0.9 -2,-2 V 2 c 0,-1.1 0.9,-2 2,-2 h 20.165 c 1.1,0 2,0.9 2,2 v 28 c 0,1.1 -0.9,2 -2,2 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <g
+           id="g1484">
+          <g
+             id="g1482">
+            <defs
+               id="defs1465">
+              <polygon
+                 points="28.082,6.689 28.082,32 3.918,32 3.918,0 21.394,0 "
+                 id="polygon1623" />
+            </defs>
+            <clipPath
+               id="c">
+              <use
+                 id="use1467"
+                 overflow="visible"
+                 xlink:href="#d"
+                 style="overflow:visible"
+                 x="0"
+                 y="0"
+                 width="616.53546"
+                 height="212.59842" />
+            </clipPath>
+            <g
+               id="g1480"
+               clip-path="url(#c)">
+              <rect
+                 id="rect1470"
+                 height="9.3380003"
+                 width="25.02"
+                 y="17.745001"
+                 x="3.493"
+                 style="fill:#000000" />
+              <g
+                 id="g1474">
+                <rect
+                   id="rect1472"
+                   height="0.5"
+                   width="25.025999"
+                   y="17.431999"
+                   x="3.487"
+                   style="fill:#000000" />
+              </g>
+              <g
+                 id="g1478">
+                <rect
+                   id="rect1476"
+                   height="0.5"
+                   width="25.025999"
+                   y="26.833"
+                   x="3.487"
+                   style="fill:#000000" />
+              </g>
+            </g>
+          </g>
+        </g>
+        <g
+           id="g1494">
+          <defs
+             id="defs1487">
+            <path
+               d="M 26.082,32 H 5.917 c -1.1,0 -2,-0.9 -2,-2 V 2 c 0,-1.1 0.9,-2 2,-2 h 20.165 c 1.1,0 2,0.9 2,2 v 28 c 0,1.1 -0.9,2 -2,2 z"
+               id="path1636"
+               inkscape:connector-curvature="0" />
+          </defs>
+          <clipPath
+             id="a">
+            <use
+               id="use1489"
+               overflow="visible"
+               xlink:href="#b"
+               style="overflow:visible"
+               x="0"
+               y="0"
+               width="616.53546"
+               height="212.59842" />
+          </clipPath>
+          <polyline
+             id="polyline1492"
+             points="21.344,-0.234 28.406,-0.234 28.406,6.828    "
+             clip-path="url(#a)"
+             style="fill:#000000" />
+        </g>
+        <polygon
+           id="polygon1496"
+           points="28.083,7.061 28.083,6.354 21.729,0 21.021,0 "
+           style="fill:#000000" />
+      </g>
+      <g
+         id="g1508"
+         display="inline"
+         style="display:inline">
+        <path
+           id="path1500"
+           d="m 10.771,19.914 v 4.922 H 9.837 V 22.708 H 7.529 v 2.128 H 6.595 v -4.922 h 0.934 v 2.008 h 2.308 v -2.008 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <path
+           id="path1502"
+           d="m 13.599,20.75 v 4.086 H 12.665 V 20.75 h -1.421 v -0.836 h 3.826 v 0.836 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <path
+           id="path1504"
+           d="m 21.297,19.914 v 4.922 h -0.934 v -2.683 c 0,-0.214 0.006,-0.457 0.018,-0.729 l 0.018,-0.368 0.02,-0.364 H 20.39 l -0.111,0.343 -0.109,0.343 c -0.102,0.308 -0.18,0.535 -0.234,0.682 l -1.082,2.776 h -0.85 L 16.91,22.081 C 16.85,21.93 16.771,21.702 16.672,21.399 L 16.56,21.056 16.448,20.717 h -0.029 l 0.018,0.357 0.018,0.36 c 0.014,0.278 0.021,0.518 0.021,0.718 v 2.683 h -0.934 v -4.922 h 1.522 l 0.88,2.278 c 0.061,0.159 0.139,0.386 0.238,0.682 l 0.107,0.343 0.111,0.339 h 0.033 l 0.105,-0.339 0.107,-0.339 c 0.09,-0.286 0.166,-0.512 0.23,-0.678 l 0.865,-2.286 h 1.557 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+        <path
+           id="path1506"
+           d="m 23.097,19.914 v 4.085 h 2.308 v 0.837 h -3.242 v -4.922 z"
+           inkscape:connector-curvature="0"
+           style="fill:#000000" />
+      </g>
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1516"
+       display="none"
+       style="display:none">
+      <text
+         id="text1512"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(4.9424,24.2963)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline">HTML</text>
+      <path
+         id="path1514"
+         d="M 22.556,0 H 5.08 V 17.313 H 2.755 v 9.118 0 l 2.324,2.324 v -2.324 0 V 32 H 29.244 V 6.689 Z M 26.83,6.189 H 23.054 V 2.414 Z M 3.755,18.313 h 22.093 v 7.118 H 3.755 Z M 28.245,31 H 6.08 V 26.431 H 26.848 V 17.313 H 6.08 V 1 h 15.974 v 6.189 h 6.19 V 31 Z"
+         display="inline"
+         inkscape:connector-curvature="0"
+         style="display:inline" />
+    </g>
+    <g
+       transform="translate(361.9774,354.59346)"
+       id="g1534"
+       display="none"
+       style="display:none">
+      <text
+         id="text1530"
+         font-size="7.3853px"
+         display="inline"
+         transform="translate(4.9424,24.2963)"
+         style="font-size:7.38530016px;line-height:0%;font-family:EurostileBold;display:inline">HTML</text>
+      <path
+         id="path1532"
+         d="M 22.556,0 H 5.08 V 17.313 H 2.755 v 9.118 0 l 2.324,2.324 v -2.324 0 V 32 H 29.244 V 6.689 Z M 26.83,6.189 H 23.054 V 2.414 Z M 3.755,18.313 h 22.093 v 7.118 H 3.755 Z"
+         display="inline"
+         inkscape:connector-curvature="0"
+         style="display:inline" />
+    </g>
+    <g
+       transform="matrix(0.75,0,0,0.75,-5.6494437,184.73619)"
+       id="g1794">
+      <path
+         id="path1526"
+         d="m 366.9994,383.43646 v 3.157 h 24.279 v -25.269 l -6.728,-6.731 h -17.551 v 16.959 h -2.324 v 9.576 l 2.317,2.316 z m 17.63,-28.057 6.025,6.028 h -6.025 z m -17.13,-0.286 h 16.63 v 6.814 h 6.649 v 24.186 h -23.279 v -4.818 h 21.768 v -9.723 h -21.768 z m -2.324,16.959 h 23.592 v 8.723 h -23.592 z m 1.824,9.222 v 1.471 l -1.471,-1.471 z"
+         inkscape:connector-curvature="0" />
+      <text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:Arial;-inkscape-font-specification:Arial;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:middle;fill:#000001;fill-opacity:1;stroke:none;stroke-width:1.06700003;marker:none"
+         x="376.06494"
+         y="366.7695"
+         id="text1783"><tspan
+           style="font-size:10.66670036px;line-height:1"
+           sodipodi:role="line"
+           id="tspan1781"
+           x="376.06494"
+           y="366.7695">{x}</tspan></text>
+      <text
+         id="text1787"
+         y="378.48114"
+         x="376.73477"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:Arial;-inkscape-font-specification:Arial;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:middle;fill:#000001;fill-opacity:1;stroke:none;stroke-width:0.52676201;marker:none"
+         xml:space="preserve"><tspan
+           style="font-size:5.26597977px;line-height:1"
+           y="378.48114"
+           x="376.73477"
+           id="tspan1785"
+           sodipodi:role="line">SCHEMA</tspan></text>
+    </g>
+    <g
+       transform="matrix(0.63938085,0,0,0.63938085,230.86346,505.4649)"
+       id="g1838"
+       style="fill-rule:evenodd">
+      <g
+         style="fill:#000000;fill-rule:evenodd"
+         id="g1836"
+         transform="matrix(0,0.46327147,-0.46327147,0,349.99468,70.425726)">
+        <g
+           transform="translate(-273.13876,667.95829)"
+           id="g1834"
+           style="fill:#000000">
+          <path
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
+             d="m 93.558594,52.710938 v 5.22559 H 81.091797 v 4.841788 h 12.466797 v 5.227543 l 6.623046,-3.824218 6.62109,-3.824219 -6.62109,-3.822266 z"
+             transform="matrix(2.1585616,0,0,2.1585616,-155.44878,-106.41793)"
+             id="path1832"
+             inkscape:connector-curvature="0"
+             sodipodi:nodetypes="cccccccccc" />
+        </g>
+      </g>
+    </g>
+    <g
+       transform="matrix(0.13238848,0,0,0.13238848,217.32806,461.05658)"
+       id="g1867">
+      <path
+         inkscape:connector-curvature="0"
+         d="m 205.77298,304.27696 29.2,-29.2 c 2,-2 2,-5.1 0,-7.1 -2,-2 -5.1,-2 -7.1,0 l -25.6,25.7 -12.7,-12.7 c -2,-2 -5.1,-2 -7.1,0 -2,2 -2,5.1 0,7.1 l 16.2,16.2 c 1,1 2.3,1.5 3.5,1.5 1.2,0 2.7,-0.6 3.6,-1.5 z"
+         id="path1863"
+         style="fill:#008000"
+         sodipodi:nodetypes="sssccssssss" />
+    </g>
+    <g
+       id="g3118"
+       transform="translate(5.2157457,-5.2157457)">
+      <text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9px;line-height:0%;font-family:Arial;-inkscape-font-specification:Arial;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:middle;fill:#000001;fill-opacity:1;stroke:none;stroke-width:0.80025005;marker:none"
+         x="223.62106"
+         y="482.91251"
+         id="text1298"><tspan
+           style="font-size:8.0000248px;line-height:1;stroke-width:0.80025005"
+           sodipodi:role="line"
+           id="tspan1296"
+           x="223.62106"
+           y="482.91251">PEP</tspan></text>
+      <g
+         transform="matrix(0.51090381,0,0,0.51090381,-39.396681,433.22104)"
+         id="g7655">
+        <rect
+           transform="rotate(-90)"
+           y="445.97046"
+           x="-78.00325"
+           height="26.577616"
+           width="36.788246"
+           id="rect7506"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#000000;stroke-width:1.68163145;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+        <text
+           xml:space="preserve"
+           style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:monospace;-inkscape-font-specification:Monospace;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate"
+           x="448.6377"
+           y="38.46571"
+           id="text7508"><tspan
+             style="font-size:9px;line-height:1.25;font-family:monospace"
+             sodipodi:role="line"
+             id="tspan7510"
+             x="448.6377"
+             y="38.46571">yaml</tspan></text>
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 448.74579,44.69502 h 10.04619"
+           id="path7518"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7520"
+           d="m 452.43501,47.1545 h 10.04618"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 452.43501,49.61397 h 10.04618"
+           id="path7522"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7525"
+           d="m 448.74579,52.07345 h 10.04619"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 452.43501,54.53292 h 10.04618"
+           id="path7527"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7529"
+           d="m 448.74579,56.9924 h 10.04619"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 452.43501,59.45188 h 10.04618"
+           id="path7531"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7533"
+           d="m 454.89448,61.91135 h 10.04619"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 448.74579,69.28978 h 10.04619"
+           id="path7535"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path7537"
+           d="m 451.20527,71.74925 h 10.04618"
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        <path
+           style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 454.89448,64.37083 h 10.04619"
+           id="path7539"
+           inkscape:connector-curvature="0" />
+        <g
+           transform="translate(-275.32507,-115.41405)"
+           id="g7541">
+          <rect
+             y="161.58936"
+             x="752.09998"
+             height="26.577616"
+             width="36.788246"
+             id="rect7545"
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#000000;stroke-width:1.68163145;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+          <text
+             xml:space="preserve"
+             style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:monospace;-inkscape-font-specification:Monospace;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate"
+             x="762.16205"
+             y="158.8647"
+             id="text7547"><tspan
+               style="font-size:9px;line-height:1.25;font-family:monospace"
+               sodipodi:role="line"
+               id="tspan7549"
+               x="762.16205"
+               y="158.8647">csv</tspan></text>
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,164.82616 h 10.04619"
+             id="path7551"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7553"
+             d="m 770.68188,164.82616 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7555"
+             d="m 780.35058,164.82616 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7557"
+             d="m 756.18726,177.12354 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,177.12354 h 5.76133"
+             id="path7559"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,177.12354 h 4.17599"
+             id="path7561"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,174.66406 h 10.04619"
+             id="path7563"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7565"
+             d="m 770.68188,174.66406 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7567"
+             d="m 780.35058,174.66406 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7569"
+             d="m 756.18726,172.20459 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,172.20459 h 5.76133"
+             id="path7571"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,172.20459 h 4.17599"
+             id="path7573"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,169.74511 h 10.04619"
+             id="path7575"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7577"
+             d="m 770.68188,169.74511 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7579"
+             d="m 780.35058,169.74511 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7581"
+             d="m 756.18726,167.28564 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,167.28564 h 5.76133"
+             id="path7583"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,167.28564 h 4.17599"
+             id="path7585"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,179.58301 h 10.04619"
+             id="path7587"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7589"
+             d="m 770.68188,179.58301 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7591"
+             d="m 780.35058,179.58301 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7593"
+             d="m 756.18726,182.04249 h 10.04619"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 770.68188,182.04249 h 5.76133"
+             id="path7595"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 780.35058,182.04249 h 4.17599"
+             id="path7597"
+             inkscape:connector-curvature="0" />
+          <path
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 756.18726,184.50197 h 10.04619"
+             id="path7599"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7601"
+             d="m 770.68188,184.50197 h 5.76133"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path7603"
+             d="m 780.35058,184.50197 h 4.17599"
+             style="fill:#4d4d4d;stroke:#666666;stroke-width:1.22973788px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+        </g>
+      </g>
+      <g
+         id="g3504"
+         transform="matrix(0.75,0,0,0.75,147.45632,111.09041)">
+        <circle
+           r="20.162695"
+           cy="73.545349"
+           cx="-173.12106"
+           transform="matrix(0.75,0,0,0.75,206.76781,428.32095)"
+           id="path3502"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#cccccc;stroke-width:1.20689654;marker:none;enable-background:accumulate" />
+        <g
+           inkscape:label="Layer 1"
+           id="layer1-6-3"
+           transform="matrix(0.77259294,0,0,0.77259294,-198.61013,38.289384)">
+          <g
+             id="g4180-7-6"
+             style="fill:#000000;fill-rule:evenodd;stroke:none"
+             transform="matrix(0.56420713,0,0,0.56420713,339.23445,559.72193)">
+            <g
+               id="g4182-5-7"
+               transform="translate(-986,-742)"
+               style="fill:#000000">
+              <g
+                 style="fill:#000000"
+                 id="g4184-3-8"
+                 transform="translate(986,742)">
+                <path
+                   style="fill:#000000"
+                   id="path4188-5-5"
+                   d="M 30,50 C 18.972,50 10,41.028 10,30 c 0,-0.33 0.009,-0.664 0.025,-1 0.185,-0.001 0.359,-0.036 0.538,-0.057 0.151,-0.017 0.307,-0.018 0.454,-0.048 0.177,-0.037 0.34,-0.104 0.51,-0.159 0.142,-0.046 0.29,-0.076 0.425,-0.134 0.16,-0.068 0.304,-0.163 0.455,-0.247 0.131,-0.073 0.27,-0.132 0.393,-0.215 0.14,-0.095 0.26,-0.214 0.39,-0.322 0.116,-0.097 0.241,-0.182 0.348,-0.289 0.12,-0.121 0.218,-0.262 0.325,-0.394 0.094,-0.116 0.2,-0.222 0.284,-0.346 0.1,-0.147 0.172,-0.312 0.255,-0.469 0.068,-0.127 0.149,-0.246 0.205,-0.379 0.08,-0.188 0.128,-0.391 0.184,-0.59 0.033,-0.117 0.083,-0.226 0.107,-0.346 C 14.965,24.68 15,24.344 15,24 15,23.702 14.964,23.411 14.911,23.124 14.898,23.047 14.884,22.97 14.867,22.893 14.808,22.64 14.734,22.393 14.637,22.154 14.596,22.053 14.547,21.958 14.5,21.86 14.429,21.712 14.351,21.568 14.265,21.428 14.191,21.305 14.116,21.185 14.032,21.07 13.914,20.908 13.786,20.753 13.649,20.606 13.555,20.505 13.457,20.411 13.355,20.319 13.252,20.224 13.143,20.137 13.031,20.052 12.945,19.985 12.869,19.908 12.778,19.847 16.374,13.748 22.883,10 30,10 41.028,10 50,18.972 50,30 50,41.028 41.028,50 30,50 M 52,30 C 52,17.869 42.131,8 30,8 22.072,8 14.841,12.234 10.918,19.093 10.62,19.037 10.315,19 10,19 7.243,19 5,21.243 5,24 5,26.073 6.269,27.854 8.07,28.611 8.041,29.076 8,29.544 8,30 8,42.131 17.869,52 30,52 42.131,52 52,42.131 52,30"
+                   inkscape:connector-curvature="0" />
+                <path
+                   style="fill:#000000"
+                   id="path4190-6-3"
+                   d="m 43.2881,54.6436 c -4.071,2.198 -8.646,3.356 -13.288,3.356 -15.44,0 -28,-12.56 -28,-28 0,-15.439 12.56,-28 28,-28 5.296,0 10.454,1.492 14.922,4.314 l 0.26,0.247 c -0.115,0.462 -0.182,0.943 -0.182,1.439 0,3.309 2.691,6 6,6 0.263,0 0.521,-0.022 0.775,-0.055 0.069,-0.009 0.136,-0.02 0.204,-0.032 0.219,-0.036 0.434,-0.083 0.645,-0.142 0.029,-0.008 0.06,-0.011 0.089,-0.02 l 0.255,0.244 c 3.292,4.714 5.032,10.247 5.032,16.005 0,7.001 -2.574,13.645 -7.269,18.811 -0.235,-0.154 -0.484,-0.28 -0.74,-0.392 -0.053,-0.022 -0.107,-0.039 -0.161,-0.06 -0.225,-0.089 -0.456,-0.161 -0.693,-0.216 -0.077,-0.018 -0.152,-0.036 -0.23,-0.051 -0.297,-0.054 -0.599,-0.092 -0.907,-0.092 -2.757,0 -5,2.244 -5,5 0,0.303 0.034,0.603 0.091,0.901 0.03,0.166 0.082,0.323 0.128,0.483 0.026,0.086 0.039,0.175 0.069,0.26 m 16.712,-24.644 c 0,-6.187 -1.877,-12.131 -5.424,-17.193 1.468,-1.095 2.424,-2.839 2.424,-4.807 0,-3.308 -2.691,-6 -6,-6 -2.068,0 -3.894,1.052 -4.974,2.648 -4.797,-3.04 -10.337,-4.648 -16.026,-4.648 -16.542,0 -30,13.458 -30,30 0,16.542 13.458,30 30,30 5.005,0 9.929,-1.27 14.311,-3.654 0.915,1.009 2.223,1.654 3.689,1.654 2.757,0 5,-2.243 5,-5 0,-1.031 -0.315,-1.991 -0.852,-2.788 5.066,-5.542 7.852,-12.682 7.852,-20.212"
+                   inkscape:connector-curvature="0" />
+              </g>
+            </g>
+          </g>
+          <path
+             inkscape:connector-curvature="0"
+             style="fill:#000000"
+             d="m 363.07286,583.10964 c 1.64873,-1.64873 1.64875,-4.31724 0,-5.96599 l -6.71633,-6.71632 c -0.99716,-0.99717 -2.36772,-1.38806 -3.66033,-1.17905 -0.0432,0.006 -0.0857,0.0112 -0.12771,0.0182 -0.0117,0.002 -0.0248,0.007 -0.0365,0.009 -0.65273,0.13194 -1.27922,0.41721 -1.82216,0.85976 -0.11095,0.0899 -0.21765,0.18574 -0.32156,0.28965 -1.64758,1.64758 -1.64875,4.32183 -2e-5,5.97056 l 6.71633,6.71633 c 1.64987,1.64761 4.3207,1.64528 5.96828,-0.002 z m -0.49943,-0.49943 c -1.36967,1.36967 -3.59744,1.36969 -4.96713,0 l -3.35932,-3.35932 4.96711,-4.96711 3.35932,3.35932 c 1.36969,1.36969 1.36967,3.59746 2e-5,4.96711 z m -10.48385,-6.65698 c -0.10043,0.13076 -0.30148,0.13602 -0.42876,0.0227 -0.0163,-0.014 -1.81286,-1.66965 -0.8963,-3.94311 0.014,-0.0397 0.0382,-0.0746 0.0662,-0.10262 0.0829,-0.0829 0.21392,-0.11514 0.32837,-0.0684 0.15764,0.0642 0.23183,0.24037 0.16876,0.39683 -0.76014,1.88344 0.72485,3.24948 0.74124,3.26354 0.12489,0.11322 0.13375,0.30605 0.0205,0.43104 z"
+             id="path4220-2-4" />
+          <ellipse
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.00000024;marker:none;enable-background:accumulate"
+             id="path4248-7"
+             cx="344.8399"
+             cy="573.29431"
+             rx="2.3967094"
+             ry="2.3335748" />
+          <circle
+             r="2.3335748"
+             cy="589.61456"
+             cx="366.30563"
+             id="circle4250-5"
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.00000024;marker:none;enable-background:accumulate" />
+          <circle
+             style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.00000024;marker:none;enable-background:accumulate"
+             id="circle4252-6"
+             cx="367.95255"
+             cy="564.29224"
+             r="2.9017856" />
+        </g>
+      </g>
+    </g>
+    <text
+       id="text3587"
+       y="473.0827"
+       x="243.24286"
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8.53124809px;line-height:0%;font-family:Arial;-inkscape-font-specification:Arial;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.71093732px;marker:none;enable-background:accumulate"
+       xml:space="preserve"><tspan
+         style="font-size:27.1120224px;line-height:1.25;stroke-width:0.71093732px"
+         y="473.0827"
+         x="243.24286"
+         id="tspan3589"
+         sodipodi:role="line">+</tspan></text>
+    <text
+       id="text3895"
+       y="502.14142"
+       x="250.39359"
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:Arial;-inkscape-font-specification:Arial;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate"
+       xml:space="preserve"><tspan
+         style="font-size:10px;line-height:1.25"
+         y="502.14142"
+         x="250.39359"
+         id="tspan3897"
+         sodipodi:role="line">Valid</tspan></text>
+    <g
+       id="g5400"
+       transform="matrix(0.46564321,0,0,0.46534938,368.59824,407.59081)"
+       style="stroke-width:1.47446001" />
+    <text
+       xml:space="preserve"
+       id="text1362"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect1364);fill:#000000;fill-opacity:1;stroke:none"
+       x="101.8434"
+       y="233.31212" />
+    <text
+       xml:space="preserve"
+       id="text8092"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect8094);fill:#000000;fill-opacity:1;stroke:none" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
+       x="510.64374"
+       y="398.73453"
+       id="text8348"><tspan
+         sodipodi:role="line"
+         id="tspan8346"
+         x="510.64374"
+         y="434.12515" /></text>
+    <text
+       id="text8012"
+       y="482.03729"
+       x="566.41211"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
+       xml:space="preserve"><tspan
+         y="517.42792"
+         x="566.41211"
+         id="tspan8010"
+         sodipodi:role="line" /></text>
+    <text
+       id="text8034"
+       y="584.48798"
+       x="582.87738"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
+       xml:space="preserve"><tspan
+         y="619.8786"
+         x="582.87738"
+         id="tspan8032"
+         sodipodi:role="line" /></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.3961668px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.41214061;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       x="256.08963"
+       y="482.83575"
+       id="text1156"><tspan
+         sodipodi:role="line"
+         id="tspan1154"
+         x="256.08963"
+         y="482.83575"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.03442717px;line-height:0;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.41214061">eido</tspan><tspan
+         sodipodi:role="line"
+         x="256.08963"
+         y="488.33096"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.03442717px;line-height:0;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.41214061"
+         id="tspan1158">validate</tspan></text>
+  </g>
+</svg>
diff --git a/webeido/static/yes.png b/docs/img/yes.png
similarity index 100%
rename from webeido/static/yes.png
rename to docs/img/yes.png
diff --git a/docs/install.md b/docs/install.md
index fbb7ddc6..cbd54878 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -11,4 +11,4 @@ See if your install worked by calling `eido -h` on the command line. If the `eid
 
 ```{console}
 export PATH=~/.local/bin:$PATH
-```
\ No newline at end of file
+```
diff --git a/docs/nunjucks.js b/docs/nunjucks.js
new file mode 100644
index 00000000..363c5369
--- /dev/null
+++ b/docs/nunjucks.js
@@ -0,0 +1,8257 @@
+/*! Browser bundle of nunjucks 3.2.2  */
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["nunjucks"] = factory();
+	else
+		root["nunjucks"] = factory();
+})(typeof self !== 'undefined' ? self : this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 11);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var ArrayProto = Array.prototype;
+var ObjProto = Object.prototype;
+var escapeMap = {
+  '&': '&amp;',
+  '"': '&quot;',
+  '\'': '&#39;',
+  '<': '&lt;',
+  '>': '&gt;'
+};
+var escapeRegex = /[&"'<>]/g;
+var exports = module.exports = {};
+
+function hasOwnProp(obj, k) {
+  return ObjProto.hasOwnProperty.call(obj, k);
+}
+
+exports.hasOwnProp = hasOwnProp;
+
+function lookupEscape(ch) {
+  return escapeMap[ch];
+}
+
+function _prettifyError(path, withInternals, err) {
+  if (!err.Update) {
+    // not one of ours, cast it
+    err = new exports.TemplateError(err);
+  }
+
+  err.Update(path); // Unless they marked the dev flag, show them a trace from here
+
+  if (!withInternals) {
+    var old = err;
+    err = new Error(old.message);
+    err.name = old.name;
+  }
+
+  return err;
+}
+
+exports._prettifyError = _prettifyError;
+
+function TemplateError(message, lineno, colno) {
+  var err;
+  var cause;
+
+  if (message instanceof Error) {
+    cause = message;
+    message = cause.name + ": " + cause.message;
+  }
+
+  if (Object.setPrototypeOf) {
+    err = new Error(message);
+    Object.setPrototypeOf(err, TemplateError.prototype);
+  } else {
+    err = this;
+    Object.defineProperty(err, 'message', {
+      enumerable: false,
+      writable: true,
+      value: message
+    });
+  }
+
+  Object.defineProperty(err, 'name', {
+    value: 'Template render error'
+  });
+
+  if (Error.captureStackTrace) {
+    Error.captureStackTrace(err, this.constructor);
+  }
+
+  var getStack;
+
+  if (cause) {
+    var stackDescriptor = Object.getOwnPropertyDescriptor(cause, 'stack');
+
+    getStack = stackDescriptor && (stackDescriptor.get || function () {
+      return stackDescriptor.value;
+    });
+
+    if (!getStack) {
+      getStack = function getStack() {
+        return cause.stack;
+      };
+    }
+  } else {
+    var stack = new Error(message).stack;
+
+    getStack = function getStack() {
+      return stack;
+    };
+  }
+
+  Object.defineProperty(err, 'stack', {
+    get: function get() {
+      return getStack.call(err);
+    }
+  });
+  Object.defineProperty(err, 'cause', {
+    value: cause
+  });
+  err.lineno = lineno;
+  err.colno = colno;
+  err.firstUpdate = true;
+
+  err.Update = function Update(path) {
+    var msg = '(' + (path || 'unknown path') + ')'; // only show lineno + colno next to path of template
+    // where error occurred
+
+    if (this.firstUpdate) {
+      if (this.lineno && this.colno) {
+        msg += " [Line " + this.lineno + ", Column " + this.colno + "]";
+      } else if (this.lineno) {
+        msg += " [Line " + this.lineno + "]";
+      }
+    }
+
+    msg += '\n ';
+
+    if (this.firstUpdate) {
+      msg += ' ';
+    }
+
+    this.message = msg + (this.message || '');
+    this.firstUpdate = false;
+    return this;
+  };
+
+  return err;
+}
+
+if (Object.setPrototypeOf) {
+  Object.setPrototypeOf(TemplateError.prototype, Error.prototype);
+} else {
+  TemplateError.prototype = Object.create(Error.prototype, {
+    constructor: {
+      value: TemplateError
+    }
+  });
+}
+
+exports.TemplateError = TemplateError;
+
+function escape(val) {
+  return val.replace(escapeRegex, lookupEscape);
+}
+
+exports.escape = escape;
+
+function isFunction(obj) {
+  return ObjProto.toString.call(obj) === '[object Function]';
+}
+
+exports.isFunction = isFunction;
+
+function isArray(obj) {
+  return ObjProto.toString.call(obj) === '[object Array]';
+}
+
+exports.isArray = isArray;
+
+function isString(obj) {
+  return ObjProto.toString.call(obj) === '[object String]';
+}
+
+exports.isString = isString;
+
+function isObject(obj) {
+  return ObjProto.toString.call(obj) === '[object Object]';
+}
+
+exports.isObject = isObject;
+/**
+ * @param {string|number} attr
+ * @returns {(string|number)[]}
+ * @private
+ */
+
+function _prepareAttributeParts(attr) {
+  if (!attr) {
+    return [];
+  }
+
+  if (typeof attr === 'string') {
+    return attr.split('.');
+  }
+
+  return [attr];
+}
+/**
+ * @param {string}   attribute      Attribute value. Dots allowed.
+ * @returns {function(Object): *}
+ */
+
+
+function getAttrGetter(attribute) {
+  var parts = _prepareAttributeParts(attribute);
+
+  return function attrGetter(item) {
+    var _item = item;
+
+    for (var i = 0; i < parts.length; i++) {
+      var part = parts[i]; // If item is not an object, and we still got parts to handle, it means
+      // that something goes wrong. Just roll out to undefined in that case.
+
+      if (hasOwnProp(_item, part)) {
+        _item = _item[part];
+      } else {
+        return undefined;
+      }
+    }
+
+    return _item;
+  };
+}
+
+function groupBy(obj, val, throwOnUndefined) {
+  var result = {};
+  var iterator = isFunction(val) ? val : getAttrGetter(val);
+
+  for (var i = 0; i < obj.length; i++) {
+    var value = obj[i];
+    var key = iterator(value, i);
+
+    if (key === undefined && throwOnUndefined === true) {
+      throw new TypeError("groupby: attribute \"" + val + "\" resolved to undefined");
+    }
+
+    (result[key] || (result[key] = [])).push(value);
+  }
+
+  return result;
+}
+
+exports.groupBy = groupBy;
+
+function toArray(obj) {
+  return Array.prototype.slice.call(obj);
+}
+
+exports.toArray = toArray;
+
+function without(array) {
+  var result = [];
+
+  if (!array) {
+    return result;
+  }
+
+  var length = array.length;
+  var contains = toArray(arguments).slice(1);
+  var index = -1;
+
+  while (++index < length) {
+    if (indexOf(contains, array[index]) === -1) {
+      result.push(array[index]);
+    }
+  }
+
+  return result;
+}
+
+exports.without = without;
+
+function repeat(char_, n) {
+  var str = '';
+
+  for (var i = 0; i < n; i++) {
+    str += char_;
+  }
+
+  return str;
+}
+
+exports.repeat = repeat;
+
+function each(obj, func, context) {
+  if (obj == null) {
+    return;
+  }
+
+  if (ArrayProto.forEach && obj.forEach === ArrayProto.forEach) {
+    obj.forEach(func, context);
+  } else if (obj.length === +obj.length) {
+    for (var i = 0, l = obj.length; i < l; i++) {
+      func.call(context, obj[i], i, obj);
+    }
+  }
+}
+
+exports.each = each;
+
+function map(obj, func) {
+  var results = [];
+
+  if (obj == null) {
+    return results;
+  }
+
+  if (ArrayProto.map && obj.map === ArrayProto.map) {
+    return obj.map(func);
+  }
+
+  for (var i = 0; i < obj.length; i++) {
+    results[results.length] = func(obj[i], i);
+  }
+
+  if (obj.length === +obj.length) {
+    results.length = obj.length;
+  }
+
+  return results;
+}
+
+exports.map = map;
+
+function asyncIter(arr, iter, cb) {
+  var i = -1;
+
+  function next() {
+    i++;
+
+    if (i < arr.length) {
+      iter(arr[i], i, next, cb);
+    } else {
+      cb();
+    }
+  }
+
+  next();
+}
+
+exports.asyncIter = asyncIter;
+
+function asyncFor(obj, iter, cb) {
+  var keys = keys_(obj || {});
+  var len = keys.length;
+  var i = -1;
+
+  function next() {
+    i++;
+    var k = keys[i];
+
+    if (i < len) {
+      iter(k, obj[k], i, len, next);
+    } else {
+      cb();
+    }
+  }
+
+  next();
+}
+
+exports.asyncFor = asyncFor;
+
+function indexOf(arr, searchElement, fromIndex) {
+  return Array.prototype.indexOf.call(arr || [], searchElement, fromIndex);
+}
+
+exports.indexOf = indexOf;
+
+function keys_(obj) {
+  /* eslint-disable no-restricted-syntax */
+  var arr = [];
+
+  for (var k in obj) {
+    if (hasOwnProp(obj, k)) {
+      arr.push(k);
+    }
+  }
+
+  return arr;
+}
+
+exports.keys = keys_;
+
+function _entries(obj) {
+  return keys_(obj).map(function (k) {
+    return [k, obj[k]];
+  });
+}
+
+exports._entries = _entries;
+
+function _values(obj) {
+  return keys_(obj).map(function (k) {
+    return obj[k];
+  });
+}
+
+exports._values = _values;
+
+function extend(obj1, obj2) {
+  obj1 = obj1 || {};
+  keys_(obj2).forEach(function (k) {
+    obj1[k] = obj2[k];
+  });
+  return obj1;
+}
+
+exports._assign = exports.extend = extend;
+
+function inOperator(key, val) {
+  if (isArray(val) || isString(val)) {
+    return val.indexOf(key) !== -1;
+  } else if (isObject(val)) {
+    return key in val;
+  }
+
+  throw new Error('Cannot use "in" operator to search for "' + key + '" in unexpected types.');
+}
+
+exports.inOperator = inOperator;
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+ // A simple class system, more documentation to come
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var EventEmitter = __webpack_require__(16);
+
+var lib = __webpack_require__(0);
+
+function parentWrap(parent, prop) {
+  if (typeof parent !== 'function' || typeof prop !== 'function') {
+    return prop;
+  }
+
+  return function wrap() {
+    // Save the current parent method
+    var tmp = this.parent; // Set parent to the previous method, call, and restore
+
+    this.parent = parent;
+    var res = prop.apply(this, arguments);
+    this.parent = tmp;
+    return res;
+  };
+}
+
+function extendClass(cls, name, props) {
+  props = props || {};
+  lib.keys(props).forEach(function (k) {
+    props[k] = parentWrap(cls.prototype[k], props[k]);
+  });
+
+  var subclass =
+  /*#__PURE__*/
+  function (_cls) {
+    _inheritsLoose(subclass, _cls);
+
+    function subclass() {
+      return _cls.apply(this, arguments) || this;
+    }
+
+    _createClass(subclass, [{
+      key: "typename",
+      get: function get() {
+        return name;
+      }
+    }]);
+
+    return subclass;
+  }(cls);
+
+  lib._assign(subclass.prototype, props);
+
+  return subclass;
+}
+
+var Obj =
+/*#__PURE__*/
+function () {
+  function Obj() {
+    // Unfortunately necessary for backwards compatibility
+    this.init.apply(this, arguments);
+  }
+
+  var _proto = Obj.prototype;
+
+  _proto.init = function init() {};
+
+  Obj.extend = function extend(name, props) {
+    if (typeof name === 'object') {
+      props = name;
+      name = 'anonymous';
+    }
+
+    return extendClass(this, name, props);
+  };
+
+  _createClass(Obj, [{
+    key: "typename",
+    get: function get() {
+      return this.constructor.name;
+    }
+  }]);
+
+  return Obj;
+}();
+
+var EmitterObj =
+/*#__PURE__*/
+function (_EventEmitter) {
+  _inheritsLoose(EmitterObj, _EventEmitter);
+
+  function EmitterObj() {
+    var _this2;
+
+    var _this;
+
+    _this = _EventEmitter.call(this) || this; // Unfortunately necessary for backwards compatibility
+
+    (_this2 = _this).init.apply(_this2, arguments);
+
+    return _this;
+  }
+
+  var _proto2 = EmitterObj.prototype;
+
+  _proto2.init = function init() {};
+
+  EmitterObj.extend = function extend(name, props) {
+    if (typeof name === 'object') {
+      props = name;
+      name = 'anonymous';
+    }
+
+    return extendClass(this, name, props);
+  };
+
+  _createClass(EmitterObj, [{
+    key: "typename",
+    get: function get() {
+      return this.constructor.name;
+    }
+  }]);
+
+  return EmitterObj;
+}(EventEmitter);
+
+module.exports = {
+  Obj: Obj,
+  EmitterObj: EmitterObj
+};
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var lib = __webpack_require__(0);
+
+var arrayFrom = Array.from;
+var supportsIterators = typeof Symbol === 'function' && Symbol.iterator && typeof arrayFrom === 'function'; // Frames keep track of scoping both at compile-time and run-time so
+// we know how to access variables. Block tags can introduce special
+// variables, for example.
+
+var Frame =
+/*#__PURE__*/
+function () {
+  function Frame(parent, isolateWrites) {
+    this.variables = {};
+    this.parent = parent;
+    this.topLevel = false; // if this is true, writes (set) should never propagate upwards past
+    // this frame to its parent (though reads may).
+
+    this.isolateWrites = isolateWrites;
+  }
+
+  var _proto = Frame.prototype;
+
+  _proto.set = function set(name, val, resolveUp) {
+    // Allow variables with dots by automatically creating the
+    // nested structure
+    var parts = name.split('.');
+    var obj = this.variables;
+    var frame = this;
+
+    if (resolveUp) {
+      if (frame = this.resolve(parts[0], true)) {
+        frame.set(name, val);
+        return;
+      }
+    }
+
+    for (var i = 0; i < parts.length - 1; i++) {
+      var id = parts[i];
+
+      if (!obj[id]) {
+        obj[id] = {};
+      }
+
+      obj = obj[id];
+    }
+
+    obj[parts[parts.length - 1]] = val;
+  };
+
+  _proto.get = function get(name) {
+    var val = this.variables[name];
+
+    if (val !== undefined) {
+      return val;
+    }
+
+    return null;
+  };
+
+  _proto.lookup = function lookup(name) {
+    var p = this.parent;
+    var val = this.variables[name];
+
+    if (val !== undefined) {
+      return val;
+    }
+
+    return p && p.lookup(name);
+  };
+
+  _proto.resolve = function resolve(name, forWrite) {
+    var p = forWrite && this.isolateWrites ? undefined : this.parent;
+    var val = this.variables[name];
+
+    if (val !== undefined) {
+      return this;
+    }
+
+    return p && p.resolve(name);
+  };
+
+  _proto.push = function push(isolateWrites) {
+    return new Frame(this, isolateWrites);
+  };
+
+  _proto.pop = function pop() {
+    return this.parent;
+  };
+
+  return Frame;
+}();
+
+function makeMacro(argNames, kwargNames, func) {
+  var _this = this;
+
+  return function () {
+    for (var _len = arguments.length, macroArgs = new Array(_len), _key = 0; _key < _len; _key++) {
+      macroArgs[_key] = arguments[_key];
+    }
+
+    var argCount = numArgs(macroArgs);
+    var args;
+    var kwargs = getKeywordArgs(macroArgs);
+
+    if (argCount > argNames.length) {
+      args = macroArgs.slice(0, argNames.length); // Positional arguments that should be passed in as
+      // keyword arguments (essentially default values)
+
+      macroArgs.slice(args.length, argCount).forEach(function (val, i) {
+        if (i < kwargNames.length) {
+          kwargs[kwargNames[i]] = val;
+        }
+      });
+      args.push(kwargs);
+    } else if (argCount < argNames.length) {
+      args = macroArgs.slice(0, argCount);
+
+      for (var i = argCount; i < argNames.length; i++) {
+        var arg = argNames[i]; // Keyword arguments that should be passed as
+        // positional arguments, i.e. the caller explicitly
+        // used the name of a positional arg
+
+        args.push(kwargs[arg]);
+        delete kwargs[arg];
+      }
+
+      args.push(kwargs);
+    } else {
+      args = macroArgs;
+    }
+
+    return func.apply(_this, args);
+  };
+}
+
+function makeKeywordArgs(obj) {
+  obj.__keywords = true;
+  return obj;
+}
+
+function isKeywordArgs(obj) {
+  return obj && Object.prototype.hasOwnProperty.call(obj, '__keywords');
+}
+
+function getKeywordArgs(args) {
+  var len = args.length;
+
+  if (len) {
+    var lastArg = args[len - 1];
+
+    if (isKeywordArgs(lastArg)) {
+      return lastArg;
+    }
+  }
+
+  return {};
+}
+
+function numArgs(args) {
+  var len = args.length;
+
+  if (len === 0) {
+    return 0;
+  }
+
+  var lastArg = args[len - 1];
+
+  if (isKeywordArgs(lastArg)) {
+    return len - 1;
+  } else {
+    return len;
+  }
+} // A SafeString object indicates that the string should not be
+// autoescaped. This happens magically because autoescaping only
+// occurs on primitive string objects.
+
+
+function SafeString(val) {
+  if (typeof val !== 'string') {
+    return val;
+  }
+
+  this.val = val;
+  this.length = val.length;
+}
+
+SafeString.prototype = Object.create(String.prototype, {
+  length: {
+    writable: true,
+    configurable: true,
+    value: 0
+  }
+});
+
+SafeString.prototype.valueOf = function valueOf() {
+  return this.val;
+};
+
+SafeString.prototype.toString = function toString() {
+  return this.val;
+};
+
+function copySafeness(dest, target) {
+  if (dest instanceof SafeString) {
+    return new SafeString(target);
+  }
+
+  return target.toString();
+}
+
+function markSafe(val) {
+  var type = typeof val;
+
+  if (type === 'string') {
+    return new SafeString(val);
+  } else if (type !== 'function') {
+    return val;
+  } else {
+    return function wrapSafe(args) {
+      var ret = val.apply(this, arguments);
+
+      if (typeof ret === 'string') {
+        return new SafeString(ret);
+      }
+
+      return ret;
+    };
+  }
+}
+
+function suppressValue(val, autoescape) {
+  val = val !== undefined && val !== null ? val : '';
+
+  if (autoescape && !(val instanceof SafeString)) {
+    val = lib.escape(val.toString());
+  }
+
+  return val;
+}
+
+function ensureDefined(val, lineno, colno) {
+  if (val === null || val === undefined) {
+    throw new lib.TemplateError('attempted to output null or undefined value', lineno + 1, colno + 1);
+  }
+
+  return val;
+}
+
+function memberLookup(obj, val) {
+  if (obj === undefined || obj === null) {
+    return undefined;
+  }
+
+  if (typeof obj[val] === 'function') {
+    return function () {
+      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+        args[_key2] = arguments[_key2];
+      }
+
+      return obj[val].apply(obj, args);
+    };
+  }
+
+  return obj[val];
+}
+
+function callWrap(obj, name, context, args) {
+  if (!obj) {
+    throw new Error('Unable to call `' + name + '`, which is undefined or falsey');
+  } else if (typeof obj !== 'function') {
+    throw new Error('Unable to call `' + name + '`, which is not a function');
+  }
+
+  return obj.apply(context, args);
+}
+
+function contextOrFrameLookup(context, frame, name) {
+  var val = frame.lookup(name);
+  return val !== undefined ? val : context.lookup(name);
+}
+
+function handleError(error, lineno, colno) {
+  if (error.lineno) {
+    return error;
+  } else {
+    return new lib.TemplateError(error, lineno, colno);
+  }
+}
+
+function asyncEach(arr, dimen, iter, cb) {
+  if (lib.isArray(arr)) {
+    var len = arr.length;
+    lib.asyncIter(arr, function iterCallback(item, i, next) {
+      switch (dimen) {
+        case 1:
+          iter(item, i, len, next);
+          break;
+
+        case 2:
+          iter(item[0], item[1], i, len, next);
+          break;
+
+        case 3:
+          iter(item[0], item[1], item[2], i, len, next);
+          break;
+
+        default:
+          item.push(i, len, next);
+          iter.apply(this, item);
+      }
+    }, cb);
+  } else {
+    lib.asyncFor(arr, function iterCallback(key, val, i, len, next) {
+      iter(key, val, i, len, next);
+    }, cb);
+  }
+}
+
+function asyncAll(arr, dimen, func, cb) {
+  var finished = 0;
+  var len;
+  var outputArr;
+
+  function done(i, output) {
+    finished++;
+    outputArr[i] = output;
+
+    if (finished === len) {
+      cb(null, outputArr.join(''));
+    }
+  }
+
+  if (lib.isArray(arr)) {
+    len = arr.length;
+    outputArr = new Array(len);
+
+    if (len === 0) {
+      cb(null, '');
+    } else {
+      for (var i = 0; i < arr.length; i++) {
+        var item = arr[i];
+
+        switch (dimen) {
+          case 1:
+            func(item, i, len, done);
+            break;
+
+          case 2:
+            func(item[0], item[1], i, len, done);
+            break;
+
+          case 3:
+            func(item[0], item[1], item[2], i, len, done);
+            break;
+
+          default:
+            item.push(i, len, done);
+            func.apply(this, item);
+        }
+      }
+    }
+  } else {
+    var keys = lib.keys(arr || {});
+    len = keys.length;
+    outputArr = new Array(len);
+
+    if (len === 0) {
+      cb(null, '');
+    } else {
+      for (var _i = 0; _i < keys.length; _i++) {
+        var k = keys[_i];
+        func(k, arr[k], _i, len, done);
+      }
+    }
+  }
+}
+
+function fromIterator(arr) {
+  if (typeof arr !== 'object' || arr === null || lib.isArray(arr)) {
+    return arr;
+  } else if (supportsIterators && Symbol.iterator in arr) {
+    return arrayFrom(arr);
+  } else {
+    return arr;
+  }
+}
+
+module.exports = {
+  Frame: Frame,
+  makeMacro: makeMacro,
+  makeKeywordArgs: makeKeywordArgs,
+  numArgs: numArgs,
+  suppressValue: suppressValue,
+  ensureDefined: ensureDefined,
+  memberLookup: memberLookup,
+  contextOrFrameLookup: contextOrFrameLookup,
+  callWrap: callWrap,
+  handleError: handleError,
+  isArray: lib.isArray,
+  keys: lib.keys,
+  SafeString: SafeString,
+  copySafeness: copySafeness,
+  markSafe: markSafe,
+  asyncEach: asyncEach,
+  asyncAll: asyncAll,
+  inOperator: lib.inOperator,
+  fromIterator: fromIterator
+};
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var _require = __webpack_require__(1),
+    Obj = _require.Obj;
+
+function traverseAndCheck(obj, type, results) {
+  if (obj instanceof type) {
+    results.push(obj);
+  }
+
+  if (obj instanceof Node) {
+    obj.findAll(type, results);
+  }
+}
+
+var Node =
+/*#__PURE__*/
+function (_Obj) {
+  _inheritsLoose(Node, _Obj);
+
+  function Node() {
+    return _Obj.apply(this, arguments) || this;
+  }
+
+  var _proto = Node.prototype;
+
+  _proto.init = function init(lineno, colno) {
+    var _this = this,
+        _arguments = arguments;
+
+    for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+      args[_key - 2] = arguments[_key];
+    }
+
+    this.lineno = lineno;
+    this.colno = colno;
+    this.fields.forEach(function (field, i) {
+      // The first two args are line/col numbers, so offset by 2
+      var val = _arguments[i + 2]; // Fields should never be undefined, but null. It makes
+      // testing easier to normalize values.
+
+      if (val === undefined) {
+        val = null;
+      }
+
+      _this[field] = val;
+    });
+  };
+
+  _proto.findAll = function findAll(type, results) {
+    var _this2 = this;
+
+    results = results || [];
+
+    if (this instanceof NodeList) {
+      this.children.forEach(function (child) {
+        return traverseAndCheck(child, type, results);
+      });
+    } else {
+      this.fields.forEach(function (field) {
+        return traverseAndCheck(_this2[field], type, results);
+      });
+    }
+
+    return results;
+  };
+
+  _proto.iterFields = function iterFields(func) {
+    var _this3 = this;
+
+    this.fields.forEach(function (field) {
+      func(_this3[field], field);
+    });
+  };
+
+  return Node;
+}(Obj); // Abstract nodes
+
+
+var Value =
+/*#__PURE__*/
+function (_Node) {
+  _inheritsLoose(Value, _Node);
+
+  function Value() {
+    return _Node.apply(this, arguments) || this;
+  }
+
+  _createClass(Value, [{
+    key: "typename",
+    get: function get() {
+      return 'Value';
+    }
+  }, {
+    key: "fields",
+    get: function get() {
+      return ['value'];
+    }
+  }]);
+
+  return Value;
+}(Node); // Concrete nodes
+
+
+var NodeList =
+/*#__PURE__*/
+function (_Node2) {
+  _inheritsLoose(NodeList, _Node2);
+
+  function NodeList() {
+    return _Node2.apply(this, arguments) || this;
+  }
+
+  var _proto2 = NodeList.prototype;
+
+  _proto2.init = function init(lineno, colno, nodes) {
+    _Node2.prototype.init.call(this, lineno, colno, nodes || []);
+  };
+
+  _proto2.addChild = function addChild(node) {
+    this.children.push(node);
+  };
+
+  _createClass(NodeList, [{
+    key: "typename",
+    get: function get() {
+      return 'NodeList';
+    }
+  }, {
+    key: "fields",
+    get: function get() {
+      return ['children'];
+    }
+  }]);
+
+  return NodeList;
+}(Node);
+
+var Root = NodeList.extend('Root');
+var Literal = Value.extend('Literal');
+var Symbol = Value.extend('Symbol');
+var Group = NodeList.extend('Group');
+var ArrayNode = NodeList.extend('Array');
+var Pair = Node.extend('Pair', {
+  fields: ['key', 'value']
+});
+var Dict = NodeList.extend('Dict');
+var LookupVal = Node.extend('LookupVal', {
+  fields: ['target', 'val']
+});
+var If = Node.extend('If', {
+  fields: ['cond', 'body', 'else_']
+});
+var IfAsync = If.extend('IfAsync');
+var InlineIf = Node.extend('InlineIf', {
+  fields: ['cond', 'body', 'else_']
+});
+var For = Node.extend('For', {
+  fields: ['arr', 'name', 'body', 'else_']
+});
+var AsyncEach = For.extend('AsyncEach');
+var AsyncAll = For.extend('AsyncAll');
+var Macro = Node.extend('Macro', {
+  fields: ['name', 'args', 'body']
+});
+var Caller = Macro.extend('Caller');
+var Import = Node.extend('Import', {
+  fields: ['template', 'target', 'withContext']
+});
+
+var FromImport =
+/*#__PURE__*/
+function (_Node3) {
+  _inheritsLoose(FromImport, _Node3);
+
+  function FromImport() {
+    return _Node3.apply(this, arguments) || this;
+  }
+
+  var _proto3 = FromImport.prototype;
+
+  _proto3.init = function init(lineno, colno, template, names, withContext) {
+    _Node3.prototype.init.call(this, lineno, colno, template, names || new NodeList(), withContext);
+  };
+
+  _createClass(FromImport, [{
+    key: "typename",
+    get: function get() {
+      return 'FromImport';
+    }
+  }, {
+    key: "fields",
+    get: function get() {
+      return ['template', 'names', 'withContext'];
+    }
+  }]);
+
+  return FromImport;
+}(Node);
+
+var FunCall = Node.extend('FunCall', {
+  fields: ['name', 'args']
+});
+var Filter = FunCall.extend('Filter');
+var FilterAsync = Filter.extend('FilterAsync', {
+  fields: ['name', 'args', 'symbol']
+});
+var KeywordArgs = Dict.extend('KeywordArgs');
+var Block = Node.extend('Block', {
+  fields: ['name', 'body']
+});
+var Super = Node.extend('Super', {
+  fields: ['blockName', 'symbol']
+});
+var TemplateRef = Node.extend('TemplateRef', {
+  fields: ['template']
+});
+var Extends = TemplateRef.extend('Extends');
+var Include = Node.extend('Include', {
+  fields: ['template', 'ignoreMissing']
+});
+var Set = Node.extend('Set', {
+  fields: ['targets', 'value']
+});
+var Switch = Node.extend('Switch', {
+  fields: ['expr', 'cases', 'default']
+});
+var Case = Node.extend('Case', {
+  fields: ['cond', 'body']
+});
+var Output = NodeList.extend('Output');
+var Capture = Node.extend('Capture', {
+  fields: ['body']
+});
+var TemplateData = Literal.extend('TemplateData');
+var UnaryOp = Node.extend('UnaryOp', {
+  fields: ['target']
+});
+var BinOp = Node.extend('BinOp', {
+  fields: ['left', 'right']
+});
+var In = BinOp.extend('In');
+var Is = BinOp.extend('Is');
+var Or = BinOp.extend('Or');
+var And = BinOp.extend('And');
+var Not = UnaryOp.extend('Not');
+var Add = BinOp.extend('Add');
+var Concat = BinOp.extend('Concat');
+var Sub = BinOp.extend('Sub');
+var Mul = BinOp.extend('Mul');
+var Div = BinOp.extend('Div');
+var FloorDiv = BinOp.extend('FloorDiv');
+var Mod = BinOp.extend('Mod');
+var Pow = BinOp.extend('Pow');
+var Neg = UnaryOp.extend('Neg');
+var Pos = UnaryOp.extend('Pos');
+var Compare = Node.extend('Compare', {
+  fields: ['expr', 'ops']
+});
+var CompareOperand = Node.extend('CompareOperand', {
+  fields: ['expr', 'type']
+});
+var CallExtension = Node.extend('CallExtension', {
+  init: function init(ext, prop, args, contentArgs) {
+    this.parent();
+    this.extName = ext.__name || ext;
+    this.prop = prop;
+    this.args = args || new NodeList();
+    this.contentArgs = contentArgs || [];
+    this.autoescape = ext.autoescape;
+  },
+  fields: ['extName', 'prop', 'args', 'contentArgs']
+});
+var CallExtensionAsync = CallExtension.extend('CallExtensionAsync'); // This is hacky, but this is just a debugging function anyway
+
+function print(str, indent, inline) {
+  var lines = str.split('\n');
+  lines.forEach(function (line, i) {
+    if (line && (inline && i > 0 || !inline)) {
+      process.stdout.write(' '.repeat(indent));
+    }
+
+    var nl = i === lines.length - 1 ? '' : '\n';
+    process.stdout.write("" + line + nl);
+  });
+} // Print the AST in a nicely formatted tree format for debuggin
+
+
+function printNodes(node, indent) {
+  indent = indent || 0;
+  print(node.typename + ': ', indent);
+
+  if (node instanceof NodeList) {
+    print('\n');
+    node.children.forEach(function (n) {
+      printNodes(n, indent + 2);
+    });
+  } else if (node instanceof CallExtension) {
+    print(node.extName + "." + node.prop + "\n");
+
+    if (node.args) {
+      printNodes(node.args, indent + 2);
+    }
+
+    if (node.contentArgs) {
+      node.contentArgs.forEach(function (n) {
+        printNodes(n, indent + 2);
+      });
+    }
+  } else {
+    var nodes = [];
+    var props = null;
+    node.iterFields(function (val, fieldName) {
+      if (val instanceof Node) {
+        nodes.push([fieldName, val]);
+      } else {
+        props = props || {};
+        props[fieldName] = val;
+      }
+    });
+
+    if (props) {
+      print(JSON.stringify(props, null, 2) + '\n', null, true);
+    } else {
+      print('\n');
+    }
+
+    nodes.forEach(function (_ref) {
+      var fieldName = _ref[0],
+          n = _ref[1];
+      print("[" + fieldName + "] =>", indent + 2);
+      printNodes(n, indent + 4);
+    });
+  }
+}
+
+module.exports = {
+  Node: Node,
+  Root: Root,
+  NodeList: NodeList,
+  Value: Value,
+  Literal: Literal,
+  Symbol: Symbol,
+  Group: Group,
+  Array: ArrayNode,
+  Pair: Pair,
+  Dict: Dict,
+  Output: Output,
+  Capture: Capture,
+  TemplateData: TemplateData,
+  If: If,
+  IfAsync: IfAsync,
+  InlineIf: InlineIf,
+  For: For,
+  AsyncEach: AsyncEach,
+  AsyncAll: AsyncAll,
+  Macro: Macro,
+  Caller: Caller,
+  Import: Import,
+  FromImport: FromImport,
+  FunCall: FunCall,
+  Filter: Filter,
+  FilterAsync: FilterAsync,
+  KeywordArgs: KeywordArgs,
+  Block: Block,
+  Super: Super,
+  Extends: Extends,
+  Include: Include,
+  Set: Set,
+  Switch: Switch,
+  Case: Case,
+  LookupVal: LookupVal,
+  BinOp: BinOp,
+  In: In,
+  Is: Is,
+  Or: Or,
+  And: And,
+  Not: Not,
+  Add: Add,
+  Concat: Concat,
+  Sub: Sub,
+  Mul: Mul,
+  Div: Div,
+  FloorDiv: FloorDiv,
+  Mod: Mod,
+  Pow: Pow,
+  Neg: Neg,
+  Pos: Pos,
+  Compare: Compare,
+  CompareOperand: CompareOperand,
+  CallExtension: CallExtension,
+  CallExtensionAsync: CallExtensionAsync,
+  printNodes: printNodes
+};
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports) {
+
+
+
+/***/ }),
+/* 5 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var parser = __webpack_require__(8);
+
+var transformer = __webpack_require__(17);
+
+var nodes = __webpack_require__(3);
+
+var _require = __webpack_require__(0),
+    TemplateError = _require.TemplateError;
+
+var _require2 = __webpack_require__(2),
+    Frame = _require2.Frame;
+
+var _require3 = __webpack_require__(1),
+    Obj = _require3.Obj; // These are all the same for now, but shouldn't be passed straight
+// through
+
+
+var compareOps = {
+  '==': '==',
+  '===': '===',
+  '!=': '!=',
+  '!==': '!==',
+  '<': '<',
+  '>': '>',
+  '<=': '<=',
+  '>=': '>='
+};
+
+var Compiler =
+/*#__PURE__*/
+function (_Obj) {
+  _inheritsLoose(Compiler, _Obj);
+
+  function Compiler() {
+    return _Obj.apply(this, arguments) || this;
+  }
+
+  var _proto = Compiler.prototype;
+
+  _proto.init = function init(templateName, throwOnUndefined) {
+    this.templateName = templateName;
+    this.codebuf = [];
+    this.lastId = 0;
+    this.buffer = null;
+    this.bufferStack = [];
+    this._scopeClosers = '';
+    this.inBlock = false;
+    this.throwOnUndefined = throwOnUndefined;
+  };
+
+  _proto.fail = function fail(msg, lineno, colno) {
+    if (lineno !== undefined) {
+      lineno += 1;
+    }
+
+    if (colno !== undefined) {
+      colno += 1;
+    }
+
+    throw new TemplateError(msg, lineno, colno);
+  };
+
+  _proto._pushBuffer = function _pushBuffer() {
+    var id = this._tmpid();
+
+    this.bufferStack.push(this.buffer);
+    this.buffer = id;
+
+    this._emit("var " + this.buffer + " = \"\";");
+
+    return id;
+  };
+
+  _proto._popBuffer = function _popBuffer() {
+    this.buffer = this.bufferStack.pop();
+  };
+
+  _proto._emit = function _emit(code) {
+    this.codebuf.push(code);
+  };
+
+  _proto._emitLine = function _emitLine(code) {
+    this._emit(code + '\n');
+  };
+
+  _proto._emitLines = function _emitLines() {
+    var _this = this;
+
+    for (var _len = arguments.length, lines = new Array(_len), _key = 0; _key < _len; _key++) {
+      lines[_key] = arguments[_key];
+    }
+
+    lines.forEach(function (line) {
+      return _this._emitLine(line);
+    });
+  };
+
+  _proto._emitFuncBegin = function _emitFuncBegin(node, name) {
+    this.buffer = 'output';
+    this._scopeClosers = '';
+
+    this._emitLine("function " + name + "(env, context, frame, runtime, cb) {");
+
+    this._emitLine("var lineno = " + node.lineno + ";");
+
+    this._emitLine("var colno = " + node.colno + ";");
+
+    this._emitLine("var " + this.buffer + " = \"\";");
+
+    this._emitLine('try {');
+  };
+
+  _proto._emitFuncEnd = function _emitFuncEnd(noReturn) {
+    if (!noReturn) {
+      this._emitLine('cb(null, ' + this.buffer + ');');
+    }
+
+    this._closeScopeLevels();
+
+    this._emitLine('} catch (e) {');
+
+    this._emitLine('  cb(runtime.handleError(e, lineno, colno));');
+
+    this._emitLine('}');
+
+    this._emitLine('}');
+
+    this.buffer = null;
+  };
+
+  _proto._addScopeLevel = function _addScopeLevel() {
+    this._scopeClosers += '})';
+  };
+
+  _proto._closeScopeLevels = function _closeScopeLevels() {
+    this._emitLine(this._scopeClosers + ';');
+
+    this._scopeClosers = '';
+  };
+
+  _proto._withScopedSyntax = function _withScopedSyntax(func) {
+    var _scopeClosers = this._scopeClosers;
+    this._scopeClosers = '';
+    func.call(this);
+
+    this._closeScopeLevels();
+
+    this._scopeClosers = _scopeClosers;
+  };
+
+  _proto._makeCallback = function _makeCallback(res) {
+    var err = this._tmpid();
+
+    return 'function(' + err + (res ? ',' + res : '') + ') {\n' + 'if(' + err + ') { cb(' + err + '); return; }';
+  };
+
+  _proto._tmpid = function _tmpid() {
+    this.lastId++;
+    return 't_' + this.lastId;
+  };
+
+  _proto._templateName = function _templateName() {
+    return this.templateName == null ? 'undefined' : JSON.stringify(this.templateName);
+  };
+
+  _proto._compileChildren = function _compileChildren(node, frame) {
+    var _this2 = this;
+
+    node.children.forEach(function (child) {
+      _this2.compile(child, frame);
+    });
+  };
+
+  _proto._compileAggregate = function _compileAggregate(node, frame, startChar, endChar) {
+    var _this3 = this;
+
+    if (startChar) {
+      this._emit(startChar);
+    }
+
+    node.children.forEach(function (child, i) {
+      if (i > 0) {
+        _this3._emit(',');
+      }
+
+      _this3.compile(child, frame);
+    });
+
+    if (endChar) {
+      this._emit(endChar);
+    }
+  };
+
+  _proto._compileExpression = function _compileExpression(node, frame) {
+    // TODO: I'm not really sure if this type check is worth it or
+    // not.
+    this.assertType(node, nodes.Literal, nodes.Symbol, nodes.Group, nodes.Array, nodes.Dict, nodes.FunCall, nodes.Caller, nodes.Filter, nodes.LookupVal, nodes.Compare, nodes.InlineIf, nodes.In, nodes.Is, nodes.And, nodes.Or, nodes.Not, nodes.Add, nodes.Concat, nodes.Sub, nodes.Mul, nodes.Div, nodes.FloorDiv, nodes.Mod, nodes.Pow, nodes.Neg, nodes.Pos, nodes.Compare, nodes.NodeList);
+    this.compile(node, frame);
+  };
+
+  _proto.assertType = function assertType(node) {
+    for (var _len2 = arguments.length, types = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+      types[_key2 - 1] = arguments[_key2];
+    }
+
+    if (!types.some(function (t) {
+      return node instanceof t;
+    })) {
+      this.fail("assertType: invalid type: " + node.typename, node.lineno, node.colno);
+    }
+  };
+
+  _proto.compileCallExtension = function compileCallExtension(node, frame, async) {
+    var _this4 = this;
+
+    var args = node.args;
+    var contentArgs = node.contentArgs;
+    var autoescape = typeof node.autoescape === 'boolean' ? node.autoescape : true;
+
+    if (!async) {
+      this._emit(this.buffer + " += runtime.suppressValue(");
+    }
+
+    this._emit("env.getExtension(\"" + node.extName + "\")[\"" + node.prop + "\"](");
+
+    this._emit('context');
+
+    if (args || contentArgs) {
+      this._emit(',');
+    }
+
+    if (args) {
+      if (!(args instanceof nodes.NodeList)) {
+        this.fail('compileCallExtension: arguments must be a NodeList, ' + 'use `parser.parseSignature`');
+      }
+
+      args.children.forEach(function (arg, i) {
+        // Tag arguments are passed normally to the call. Note
+        // that keyword arguments are turned into a single js
+        // object as the last argument, if they exist.
+        _this4._compileExpression(arg, frame);
+
+        if (i !== args.children.length - 1 || contentArgs.length) {
+          _this4._emit(',');
+        }
+      });
+    }
+
+    if (contentArgs.length) {
+      contentArgs.forEach(function (arg, i) {
+        if (i > 0) {
+          _this4._emit(',');
+        }
+
+        if (arg) {
+          _this4._emitLine('function(cb) {');
+
+          _this4._emitLine('if(!cb) { cb = function(err) { if(err) { throw err; }}}');
+
+          var id = _this4._pushBuffer();
+
+          _this4._withScopedSyntax(function () {
+            _this4.compile(arg, frame);
+
+            _this4._emitLine("cb(null, " + id + ");");
+          });
+
+          _this4._popBuffer();
+
+          _this4._emitLine("return " + id + ";");
+
+          _this4._emitLine('}');
+        } else {
+          _this4._emit('null');
+        }
+      });
+    }
+
+    if (async) {
+      var res = this._tmpid();
+
+      this._emitLine(', ' + this._makeCallback(res));
+
+      this._emitLine(this.buffer + " += runtime.suppressValue(" + res + ", " + autoescape + " && env.opts.autoescape);");
+
+      this._addScopeLevel();
+    } else {
+      this._emit(')');
+
+      this._emit(", " + autoescape + " && env.opts.autoescape);\n");
+    }
+  };
+
+  _proto.compileCallExtensionAsync = function compileCallExtensionAsync(node, frame) {
+    this.compileCallExtension(node, frame, true);
+  };
+
+  _proto.compileNodeList = function compileNodeList(node, frame) {
+    this._compileChildren(node, frame);
+  };
+
+  _proto.compileLiteral = function compileLiteral(node) {
+    if (typeof node.value === 'string') {
+      var val = node.value.replace(/\\/g, '\\\\');
+      val = val.replace(/"/g, '\\"');
+      val = val.replace(/\n/g, '\\n');
+      val = val.replace(/\r/g, '\\r');
+      val = val.replace(/\t/g, '\\t');
+      val = val.replace(/\u2028/g, "\\u2028");
+
+      this._emit("\"" + val + "\"");
+    } else if (node.value === null) {
+      this._emit('null');
+    } else {
+      this._emit(node.value.toString());
+    }
+  };
+
+  _proto.compileSymbol = function compileSymbol(node, frame) {
+    var name = node.value;
+    var v = frame.lookup(name);
+
+    if (v) {
+      this._emit(v);
+    } else {
+      this._emit('runtime.contextOrFrameLookup(' + 'context, frame, "' + name + '")');
+    }
+  };
+
+  _proto.compileGroup = function compileGroup(node, frame) {
+    this._compileAggregate(node, frame, '(', ')');
+  };
+
+  _proto.compileArray = function compileArray(node, frame) {
+    this._compileAggregate(node, frame, '[', ']');
+  };
+
+  _proto.compileDict = function compileDict(node, frame) {
+    this._compileAggregate(node, frame, '{', '}');
+  };
+
+  _proto.compilePair = function compilePair(node, frame) {
+    var key = node.key;
+    var val = node.value;
+
+    if (key instanceof nodes.Symbol) {
+      key = new nodes.Literal(key.lineno, key.colno, key.value);
+    } else if (!(key instanceof nodes.Literal && typeof key.value === 'string')) {
+      this.fail('compilePair: Dict keys must be strings or names', key.lineno, key.colno);
+    }
+
+    this.compile(key, frame);
+
+    this._emit(': ');
+
+    this._compileExpression(val, frame);
+  };
+
+  _proto.compileInlineIf = function compileInlineIf(node, frame) {
+    this._emit('(');
+
+    this.compile(node.cond, frame);
+
+    this._emit('?');
+
+    this.compile(node.body, frame);
+
+    this._emit(':');
+
+    if (node.else_ !== null) {
+      this.compile(node.else_, frame);
+    } else {
+      this._emit('""');
+    }
+
+    this._emit(')');
+  };
+
+  _proto.compileIn = function compileIn(node, frame) {
+    this._emit('runtime.inOperator(');
+
+    this.compile(node.left, frame);
+
+    this._emit(',');
+
+    this.compile(node.right, frame);
+
+    this._emit(')');
+  };
+
+  _proto.compileIs = function compileIs(node, frame) {
+    // first, we need to try to get the name of the test function, if it's a
+    // callable (i.e., has args) and not a symbol.
+    var right = node.right.name ? node.right.name.value // otherwise go with the symbol value
+    : node.right.value;
+
+    this._emit('env.getTest("' + right + '").call(context, ');
+
+    this.compile(node.left, frame); // compile the arguments for the callable if they exist
+
+    if (node.right.args) {
+      this._emit(',');
+
+      this.compile(node.right.args, frame);
+    }
+
+    this._emit(') === true');
+  };
+
+  _proto._binOpEmitter = function _binOpEmitter(node, frame, str) {
+    this.compile(node.left, frame);
+
+    this._emit(str);
+
+    this.compile(node.right, frame);
+  }; // ensure concatenation instead of addition
+  // by adding empty string in between
+
+
+  _proto.compileOr = function compileOr(node, frame) {
+    return this._binOpEmitter(node, frame, ' || ');
+  };
+
+  _proto.compileAnd = function compileAnd(node, frame) {
+    return this._binOpEmitter(node, frame, ' && ');
+  };
+
+  _proto.compileAdd = function compileAdd(node, frame) {
+    return this._binOpEmitter(node, frame, ' + ');
+  };
+
+  _proto.compileConcat = function compileConcat(node, frame) {
+    return this._binOpEmitter(node, frame, ' + "" + ');
+  };
+
+  _proto.compileSub = function compileSub(node, frame) {
+    return this._binOpEmitter(node, frame, ' - ');
+  };
+
+  _proto.compileMul = function compileMul(node, frame) {
+    return this._binOpEmitter(node, frame, ' * ');
+  };
+
+  _proto.compileDiv = function compileDiv(node, frame) {
+    return this._binOpEmitter(node, frame, ' / ');
+  };
+
+  _proto.compileMod = function compileMod(node, frame) {
+    return this._binOpEmitter(node, frame, ' % ');
+  };
+
+  _proto.compileNot = function compileNot(node, frame) {
+    this._emit('!');
+
+    this.compile(node.target, frame);
+  };
+
+  _proto.compileFloorDiv = function compileFloorDiv(node, frame) {
+    this._emit('Math.floor(');
+
+    this.compile(node.left, frame);
+
+    this._emit(' / ');
+
+    this.compile(node.right, frame);
+
+    this._emit(')');
+  };
+
+  _proto.compilePow = function compilePow(node, frame) {
+    this._emit('Math.pow(');
+
+    this.compile(node.left, frame);
+
+    this._emit(', ');
+
+    this.compile(node.right, frame);
+
+    this._emit(')');
+  };
+
+  _proto.compileNeg = function compileNeg(node, frame) {
+    this._emit('-');
+
+    this.compile(node.target, frame);
+  };
+
+  _proto.compilePos = function compilePos(node, frame) {
+    this._emit('+');
+
+    this.compile(node.target, frame);
+  };
+
+  _proto.compileCompare = function compileCompare(node, frame) {
+    var _this5 = this;
+
+    this.compile(node.expr, frame);
+    node.ops.forEach(function (op) {
+      _this5._emit(" " + compareOps[op.type] + " ");
+
+      _this5.compile(op.expr, frame);
+    });
+  };
+
+  _proto.compileLookupVal = function compileLookupVal(node, frame) {
+    this._emit('runtime.memberLookup((');
+
+    this._compileExpression(node.target, frame);
+
+    this._emit('),');
+
+    this._compileExpression(node.val, frame);
+
+    this._emit(')');
+  };
+
+  _proto._getNodeName = function _getNodeName(node) {
+    switch (node.typename) {
+      case 'Symbol':
+        return node.value;
+
+      case 'FunCall':
+        return 'the return value of (' + this._getNodeName(node.name) + ')';
+
+      case 'LookupVal':
+        return this._getNodeName(node.target) + '["' + this._getNodeName(node.val) + '"]';
+
+      case 'Literal':
+        return node.value.toString();
+
+      default:
+        return '--expression--';
+    }
+  };
+
+  _proto.compileFunCall = function compileFunCall(node, frame) {
+    // Keep track of line/col info at runtime by settings
+    // variables within an expression. An expression in javascript
+    // like (x, y, z) returns the last value, and x and y can be
+    // anything
+    this._emit('(lineno = ' + node.lineno + ', colno = ' + node.colno + ', ');
+
+    this._emit('runtime.callWrap('); // Compile it as normal.
+
+
+    this._compileExpression(node.name, frame); // Output the name of what we're calling so we can get friendly errors
+    // if the lookup fails.
+
+
+    this._emit(', "' + this._getNodeName(node.name).replace(/"/g, '\\"') + '", context, ');
+
+    this._compileAggregate(node.args, frame, '[', '])');
+
+    this._emit(')');
+  };
+
+  _proto.compileFilter = function compileFilter(node, frame) {
+    var name = node.name;
+    this.assertType(name, nodes.Symbol);
+
+    this._emit('env.getFilter("' + name.value + '").call(context, ');
+
+    this._compileAggregate(node.args, frame);
+
+    this._emit(')');
+  };
+
+  _proto.compileFilterAsync = function compileFilterAsync(node, frame) {
+    var name = node.name;
+    var symbol = node.symbol.value;
+    this.assertType(name, nodes.Symbol);
+    frame.set(symbol, symbol);
+
+    this._emit('env.getFilter("' + name.value + '").call(context, ');
+
+    this._compileAggregate(node.args, frame);
+
+    this._emitLine(', ' + this._makeCallback(symbol));
+
+    this._addScopeLevel();
+  };
+
+  _proto.compileKeywordArgs = function compileKeywordArgs(node, frame) {
+    this._emit('runtime.makeKeywordArgs(');
+
+    this.compileDict(node, frame);
+
+    this._emit(')');
+  };
+
+  _proto.compileSet = function compileSet(node, frame) {
+    var _this6 = this;
+
+    var ids = []; // Lookup the variable names for each identifier and create
+    // new ones if necessary
+
+    node.targets.forEach(function (target) {
+      var name = target.value;
+      var id = frame.lookup(name);
+
+      if (id === null || id === undefined) {
+        id = _this6._tmpid(); // Note: This relies on js allowing scope across
+        // blocks, in case this is created inside an `if`
+
+        _this6._emitLine('var ' + id + ';');
+      }
+
+      ids.push(id);
+    });
+
+    if (node.value) {
+      this._emit(ids.join(' = ') + ' = ');
+
+      this._compileExpression(node.value, frame);
+
+      this._emitLine(';');
+    } else {
+      this._emit(ids.join(' = ') + ' = ');
+
+      this.compile(node.body, frame);
+
+      this._emitLine(';');
+    }
+
+    node.targets.forEach(function (target, i) {
+      var id = ids[i];
+      var name = target.value; // We are running this for every var, but it's very
+      // uncommon to assign to multiple vars anyway
+
+      _this6._emitLine("frame.set(\"" + name + "\", " + id + ", true);");
+
+      _this6._emitLine('if(frame.topLevel) {');
+
+      _this6._emitLine("context.setVariable(\"" + name + "\", " + id + ");");
+
+      _this6._emitLine('}');
+
+      if (name.charAt(0) !== '_') {
+        _this6._emitLine('if(frame.topLevel) {');
+
+        _this6._emitLine("context.addExport(\"" + name + "\", " + id + ");");
+
+        _this6._emitLine('}');
+      }
+    });
+  };
+
+  _proto.compileSwitch = function compileSwitch(node, frame) {
+    var _this7 = this;
+
+    this._emit('switch (');
+
+    this.compile(node.expr, frame);
+
+    this._emit(') {');
+
+    node.cases.forEach(function (c, i) {
+      _this7._emit('case ');
+
+      _this7.compile(c.cond, frame);
+
+      _this7._emit(': ');
+
+      _this7.compile(c.body, frame); // preserve fall-throughs
+
+
+      if (c.body.children.length) {
+        _this7._emitLine('break;');
+      }
+    });
+
+    if (node.default) {
+      this._emit('default:');
+
+      this.compile(node.default, frame);
+    }
+
+    this._emit('}');
+  };
+
+  _proto.compileIf = function compileIf(node, frame, async) {
+    var _this8 = this;
+
+    this._emit('if(');
+
+    this._compileExpression(node.cond, frame);
+
+    this._emitLine(') {');
+
+    this._withScopedSyntax(function () {
+      _this8.compile(node.body, frame);
+
+      if (async) {
+        _this8._emit('cb()');
+      }
+    });
+
+    if (node.else_) {
+      this._emitLine('}\nelse {');
+
+      this._withScopedSyntax(function () {
+        _this8.compile(node.else_, frame);
+
+        if (async) {
+          _this8._emit('cb()');
+        }
+      });
+    } else if (async) {
+      this._emitLine('}\nelse {');
+
+      this._emit('cb()');
+    }
+
+    this._emitLine('}');
+  };
+
+  _proto.compileIfAsync = function compileIfAsync(node, frame) {
+    this._emit('(function(cb) {');
+
+    this.compileIf(node, frame, true);
+
+    this._emit('})(' + this._makeCallback());
+
+    this._addScopeLevel();
+  };
+
+  _proto._emitLoopBindings = function _emitLoopBindings(node, arr, i, len) {
+    var _this9 = this;
+
+    var bindings = [{
+      name: 'index',
+      val: i + " + 1"
+    }, {
+      name: 'index0',
+      val: i
+    }, {
+      name: 'revindex',
+      val: len + " - " + i
+    }, {
+      name: 'revindex0',
+      val: len + " - " + i + " - 1"
+    }, {
+      name: 'first',
+      val: i + " === 0"
+    }, {
+      name: 'last',
+      val: i + " === " + len + " - 1"
+    }, {
+      name: 'length',
+      val: len
+    }];
+    bindings.forEach(function (b) {
+      _this9._emitLine("frame.set(\"loop." + b.name + "\", " + b.val + ");");
+    });
+  };
+
+  _proto.compileFor = function compileFor(node, frame) {
+    var _this10 = this;
+
+    // Some of this code is ugly, but it keeps the generated code
+    // as fast as possible. ForAsync also shares some of this, but
+    // not much.
+    var i = this._tmpid();
+
+    var len = this._tmpid();
+
+    var arr = this._tmpid();
+
+    frame = frame.push();
+
+    this._emitLine('frame = frame.push();');
+
+    this._emit("var " + arr + " = ");
+
+    this._compileExpression(node.arr, frame);
+
+    this._emitLine(';');
+
+    this._emit("if(" + arr + ") {");
+
+    this._emitLine(arr + ' = runtime.fromIterator(' + arr + ');'); // If multiple names are passed, we need to bind them
+    // appropriately
+
+
+    if (node.name instanceof nodes.Array) {
+      this._emitLine("var " + i + ";"); // The object could be an arroy or object. Note that the
+      // body of the loop is duplicated for each condition, but
+      // we are optimizing for speed over size.
+
+
+      this._emitLine("if(runtime.isArray(" + arr + ")) {");
+
+      this._emitLine("var " + len + " = " + arr + ".length;");
+
+      this._emitLine("for(" + i + "=0; " + i + " < " + arr + ".length; " + i + "++) {"); // Bind each declared var
+
+
+      node.name.children.forEach(function (child, u) {
+        var tid = _this10._tmpid();
+
+        _this10._emitLine("var " + tid + " = " + arr + "[" + i + "][" + u + "];");
+
+        _this10._emitLine("frame.set(\"" + child + "\", " + arr + "[" + i + "][" + u + "]);");
+
+        frame.set(node.name.children[u].value, tid);
+      });
+
+      this._emitLoopBindings(node, arr, i, len);
+
+      this._withScopedSyntax(function () {
+        _this10.compile(node.body, frame);
+      });
+
+      this._emitLine('}');
+
+      this._emitLine('} else {'); // Iterate over the key/values of an object
+
+
+      var _node$name$children = node.name.children,
+          key = _node$name$children[0],
+          val = _node$name$children[1];
+
+      var k = this._tmpid();
+
+      var v = this._tmpid();
+
+      frame.set(key.value, k);
+      frame.set(val.value, v);
+
+      this._emitLine(i + " = -1;");
+
+      this._emitLine("var " + len + " = runtime.keys(" + arr + ").length;");
+
+      this._emitLine("for(var " + k + " in " + arr + ") {");
+
+      this._emitLine(i + "++;");
+
+      this._emitLine("var " + v + " = " + arr + "[" + k + "];");
+
+      this._emitLine("frame.set(\"" + key.value + "\", " + k + ");");
+
+      this._emitLine("frame.set(\"" + val.value + "\", " + v + ");");
+
+      this._emitLoopBindings(node, arr, i, len);
+
+      this._withScopedSyntax(function () {
+        _this10.compile(node.body, frame);
+      });
+
+      this._emitLine('}');
+
+      this._emitLine('}');
+    } else {
+      // Generate a typical array iteration
+      var _v = this._tmpid();
+
+      frame.set(node.name.value, _v);
+
+      this._emitLine("var " + len + " = " + arr + ".length;");
+
+      this._emitLine("for(var " + i + "=0; " + i + " < " + arr + ".length; " + i + "++) {");
+
+      this._emitLine("var " + _v + " = " + arr + "[" + i + "];");
+
+      this._emitLine("frame.set(\"" + node.name.value + "\", " + _v + ");");
+
+      this._emitLoopBindings(node, arr, i, len);
+
+      this._withScopedSyntax(function () {
+        _this10.compile(node.body, frame);
+      });
+
+      this._emitLine('}');
+    }
+
+    this._emitLine('}');
+
+    if (node.else_) {
+      this._emitLine('if (!' + len + ') {');
+
+      this.compile(node.else_, frame);
+
+      this._emitLine('}');
+    }
+
+    this._emitLine('frame = frame.pop();');
+  };
+
+  _proto._compileAsyncLoop = function _compileAsyncLoop(node, frame, parallel) {
+    var _this11 = this;
+
+    // This shares some code with the For tag, but not enough to
+    // worry about. This iterates across an object asynchronously,
+    // but not in parallel.
+    var i = this._tmpid();
+
+    var len = this._tmpid();
+
+    var arr = this._tmpid();
+
+    var asyncMethod = parallel ? 'asyncAll' : 'asyncEach';
+    frame = frame.push();
+
+    this._emitLine('frame = frame.push();');
+
+    this._emit('var ' + arr + ' = runtime.fromIterator(');
+
+    this._compileExpression(node.arr, frame);
+
+    this._emitLine(');');
+
+    if (node.name instanceof nodes.Array) {
+      var arrayLen = node.name.children.length;
+
+      this._emit("runtime." + asyncMethod + "(" + arr + ", " + arrayLen + ", function(");
+
+      node.name.children.forEach(function (name) {
+        _this11._emit(name.value + ",");
+      });
+
+      this._emit(i + ',' + len + ',next) {');
+
+      node.name.children.forEach(function (name) {
+        var id = name.value;
+        frame.set(id, id);
+
+        _this11._emitLine("frame.set(\"" + id + "\", " + id + ");");
+      });
+    } else {
+      var id = node.name.value;
+
+      this._emitLine("runtime." + asyncMethod + "(" + arr + ", 1, function(" + id + ", " + i + ", " + len + ",next) {");
+
+      this._emitLine('frame.set("' + id + '", ' + id + ');');
+
+      frame.set(id, id);
+    }
+
+    this._emitLoopBindings(node, arr, i, len);
+
+    this._withScopedSyntax(function () {
+      var buf;
+
+      if (parallel) {
+        buf = _this11._pushBuffer();
+      }
+
+      _this11.compile(node.body, frame);
+
+      _this11._emitLine('next(' + i + (buf ? ',' + buf : '') + ');');
+
+      if (parallel) {
+        _this11._popBuffer();
+      }
+    });
+
+    var output = this._tmpid();
+
+    this._emitLine('}, ' + this._makeCallback(output));
+
+    this._addScopeLevel();
+
+    if (parallel) {
+      this._emitLine(this.buffer + ' += ' + output + ';');
+    }
+
+    if (node.else_) {
+      this._emitLine('if (!' + arr + '.length) {');
+
+      this.compile(node.else_, frame);
+
+      this._emitLine('}');
+    }
+
+    this._emitLine('frame = frame.pop();');
+  };
+
+  _proto.compileAsyncEach = function compileAsyncEach(node, frame) {
+    this._compileAsyncLoop(node, frame);
+  };
+
+  _proto.compileAsyncAll = function compileAsyncAll(node, frame) {
+    this._compileAsyncLoop(node, frame, true);
+  };
+
+  _proto._compileMacro = function _compileMacro(node, frame) {
+    var _this12 = this;
+
+    var args = [];
+    var kwargs = null;
+
+    var funcId = 'macro_' + this._tmpid();
+
+    var keepFrame = frame !== undefined; // Type check the definition of the args
+
+    node.args.children.forEach(function (arg, i) {
+      if (i === node.args.children.length - 1 && arg instanceof nodes.Dict) {
+        kwargs = arg;
+      } else {
+        _this12.assertType(arg, nodes.Symbol);
+
+        args.push(arg);
+      }
+    });
+    var realNames = args.map(function (n) {
+      return "l_" + n.value;
+    }).concat(['kwargs']); // Quoted argument names
+
+    var argNames = args.map(function (n) {
+      return "\"" + n.value + "\"";
+    });
+    var kwargNames = (kwargs && kwargs.children || []).map(function (n) {
+      return "\"" + n.key.value + "\"";
+    }); // We pass a function to makeMacro which destructures the
+    // arguments so support setting positional args with keywords
+    // args and passing keyword args as positional args
+    // (essentially default values). See runtime.js.
+
+    var currFrame;
+
+    if (keepFrame) {
+      currFrame = frame.push(true);
+    } else {
+      currFrame = new Frame();
+    }
+
+    this._emitLines("var " + funcId + " = runtime.makeMacro(", "[" + argNames.join(', ') + "], ", "[" + kwargNames.join(', ') + "], ", "function (" + realNames.join(', ') + ") {", 'var callerFrame = frame;', 'frame = ' + (keepFrame ? 'frame.push(true);' : 'new runtime.Frame();'), 'kwargs = kwargs || {};', 'if (Object.prototype.hasOwnProperty.call(kwargs, "caller")) {', 'frame.set("caller", kwargs.caller); }'); // Expose the arguments to the template. Don't need to use
+    // random names because the function
+    // will create a new run-time scope for us
+
+
+    args.forEach(function (arg) {
+      _this12._emitLine("frame.set(\"" + arg.value + "\", l_" + arg.value + ");");
+
+      currFrame.set(arg.value, "l_" + arg.value);
+    }); // Expose the keyword arguments
+
+    if (kwargs) {
+      kwargs.children.forEach(function (pair) {
+        var name = pair.key.value;
+
+        _this12._emit("frame.set(\"" + name + "\", ");
+
+        _this12._emit("Object.prototype.hasOwnProperty.call(kwargs, \"" + name + "\")");
+
+        _this12._emit(" ? kwargs[\"" + name + "\"] : ");
+
+        _this12._compileExpression(pair.value, currFrame);
+
+        _this12._emit(');');
+      });
+    }
+
+    var bufferId = this._pushBuffer();
+
+    this._withScopedSyntax(function () {
+      _this12.compile(node.body, currFrame);
+    });
+
+    this._emitLine('frame = ' + (keepFrame ? 'frame.pop();' : 'callerFrame;'));
+
+    this._emitLine("return new runtime.SafeString(" + bufferId + ");");
+
+    this._emitLine('});');
+
+    this._popBuffer();
+
+    return funcId;
+  };
+
+  _proto.compileMacro = function compileMacro(node, frame) {
+    var funcId = this._compileMacro(node); // Expose the macro to the templates
+
+
+    var name = node.name.value;
+    frame.set(name, funcId);
+
+    if (frame.parent) {
+      this._emitLine("frame.set(\"" + name + "\", " + funcId + ");");
+    } else {
+      if (node.name.value.charAt(0) !== '_') {
+        this._emitLine("context.addExport(\"" + name + "\");");
+      }
+
+      this._emitLine("context.setVariable(\"" + name + "\", " + funcId + ");");
+    }
+  };
+
+  _proto.compileCaller = function compileCaller(node, frame) {
+    // basically an anonymous "macro expression"
+    this._emit('(function (){');
+
+    var funcId = this._compileMacro(node, frame);
+
+    this._emit("return " + funcId + ";})()");
+  };
+
+  _proto._compileGetTemplate = function _compileGetTemplate(node, frame, eagerCompile, ignoreMissing) {
+    var parentTemplateId = this._tmpid();
+
+    var parentName = this._templateName();
+
+    var cb = this._makeCallback(parentTemplateId);
+
+    var eagerCompileArg = eagerCompile ? 'true' : 'false';
+    var ignoreMissingArg = ignoreMissing ? 'true' : 'false';
+
+    this._emit('env.getTemplate(');
+
+    this._compileExpression(node.template, frame);
+
+    this._emitLine(", " + eagerCompileArg + ", " + parentName + ", " + ignoreMissingArg + ", " + cb);
+
+    return parentTemplateId;
+  };
+
+  _proto.compileImport = function compileImport(node, frame) {
+    var target = node.target.value;
+
+    var id = this._compileGetTemplate(node, frame, false, false);
+
+    this._addScopeLevel();
+
+    this._emitLine(id + '.getExported(' + (node.withContext ? 'context.getVariables(), frame, ' : '') + this._makeCallback(id));
+
+    this._addScopeLevel();
+
+    frame.set(target, id);
+
+    if (frame.parent) {
+      this._emitLine("frame.set(\"" + target + "\", " + id + ");");
+    } else {
+      this._emitLine("context.setVariable(\"" + target + "\", " + id + ");");
+    }
+  };
+
+  _proto.compileFromImport = function compileFromImport(node, frame) {
+    var _this13 = this;
+
+    var importedId = this._compileGetTemplate(node, frame, false, false);
+
+    this._addScopeLevel();
+
+    this._emitLine(importedId + '.getExported(' + (node.withContext ? 'context.getVariables(), frame, ' : '') + this._makeCallback(importedId));
+
+    this._addScopeLevel();
+
+    node.names.children.forEach(function (nameNode) {
+      var name;
+      var alias;
+
+      var id = _this13._tmpid();
+
+      if (nameNode instanceof nodes.Pair) {
+        name = nameNode.key.value;
+        alias = nameNode.value.value;
+      } else {
+        name = nameNode.value;
+        alias = name;
+      }
+
+      _this13._emitLine("if(Object.prototype.hasOwnProperty.call(" + importedId + ", \"" + name + "\")) {");
+
+      _this13._emitLine("var " + id + " = " + importedId + "." + name + ";");
+
+      _this13._emitLine('} else {');
+
+      _this13._emitLine("cb(new Error(\"cannot import '" + name + "'\")); return;");
+
+      _this13._emitLine('}');
+
+      frame.set(alias, id);
+
+      if (frame.parent) {
+        _this13._emitLine("frame.set(\"" + alias + "\", " + id + ");");
+      } else {
+        _this13._emitLine("context.setVariable(\"" + alias + "\", " + id + ");");
+      }
+    });
+  };
+
+  _proto.compileBlock = function compileBlock(node) {
+    var id = this._tmpid(); // If we are executing outside a block (creating a top-level
+    // block), we really don't want to execute its code because it
+    // will execute twice: once when the child template runs and
+    // again when the parent template runs. Note that blocks
+    // within blocks will *always* execute immediately *and*
+    // wherever else they are invoked (like used in a parent
+    // template). This may have behavioral differences from jinja
+    // because blocks can have side effects, but it seems like a
+    // waste of performance to always execute huge top-level
+    // blocks twice
+
+
+    if (!this.inBlock) {
+      this._emit('(parentTemplate ? function(e, c, f, r, cb) { cb(""); } : ');
+    }
+
+    this._emit("context.getBlock(\"" + node.name.value + "\")");
+
+    if (!this.inBlock) {
+      this._emit(')');
+    }
+
+    this._emitLine('(env, context, frame, runtime, ' + this._makeCallback(id));
+
+    this._emitLine(this.buffer + " += " + id + ";");
+
+    this._addScopeLevel();
+  };
+
+  _proto.compileSuper = function compileSuper(node, frame) {
+    var name = node.blockName.value;
+    var id = node.symbol.value;
+
+    var cb = this._makeCallback(id);
+
+    this._emitLine("context.getSuper(env, \"" + name + "\", b_" + name + ", frame, runtime, " + cb);
+
+    this._emitLine(id + " = runtime.markSafe(" + id + ");");
+
+    this._addScopeLevel();
+
+    frame.set(id, id);
+  };
+
+  _proto.compileExtends = function compileExtends(node, frame) {
+    var k = this._tmpid();
+
+    var parentTemplateId = this._compileGetTemplate(node, frame, true, false); // extends is a dynamic tag and can occur within a block like
+    // `if`, so if this happens we need to capture the parent
+    // template in the top-level scope
+
+
+    this._emitLine("parentTemplate = " + parentTemplateId);
+
+    this._emitLine("for(var " + k + " in parentTemplate.blocks) {");
+
+    this._emitLine("context.addBlock(" + k + ", parentTemplate.blocks[" + k + "]);");
+
+    this._emitLine('}');
+
+    this._addScopeLevel();
+  };
+
+  _proto.compileInclude = function compileInclude(node, frame) {
+    this._emitLine('var tasks = [];');
+
+    this._emitLine('tasks.push(');
+
+    this._emitLine('function(callback) {');
+
+    var id = this._compileGetTemplate(node, frame, false, node.ignoreMissing);
+
+    this._emitLine("callback(null," + id + ");});");
+
+    this._emitLine('});');
+
+    var id2 = this._tmpid();
+
+    this._emitLine('tasks.push(');
+
+    this._emitLine('function(template, callback){');
+
+    this._emitLine('template.render(context.getVariables(), frame, ' + this._makeCallback(id2));
+
+    this._emitLine('callback(null,' + id2 + ');});');
+
+    this._emitLine('});');
+
+    this._emitLine('tasks.push(');
+
+    this._emitLine('function(result, callback){');
+
+    this._emitLine(this.buffer + " += result;");
+
+    this._emitLine('callback(null);');
+
+    this._emitLine('});');
+
+    this._emitLine('env.waterfall(tasks, function(){');
+
+    this._addScopeLevel();
+  };
+
+  _proto.compileTemplateData = function compileTemplateData(node, frame) {
+    this.compileLiteral(node, frame);
+  };
+
+  _proto.compileCapture = function compileCapture(node, frame) {
+    var _this14 = this;
+
+    // we need to temporarily override the current buffer id as 'output'
+    // so the set block writes to the capture output instead of the buffer
+    var buffer = this.buffer;
+    this.buffer = 'output';
+
+    this._emitLine('(function() {');
+
+    this._emitLine('var output = "";');
+
+    this._withScopedSyntax(function () {
+      _this14.compile(node.body, frame);
+    });
+
+    this._emitLine('return output;');
+
+    this._emitLine('})()'); // and of course, revert back to the old buffer id
+
+
+    this.buffer = buffer;
+  };
+
+  _proto.compileOutput = function compileOutput(node, frame) {
+    var _this15 = this;
+
+    var children = node.children;
+    children.forEach(function (child) {
+      // TemplateData is a special case because it is never
+      // autoescaped, so simply output it for optimization
+      if (child instanceof nodes.TemplateData) {
+        if (child.value) {
+          _this15._emit(_this15.buffer + " += ");
+
+          _this15.compileLiteral(child, frame);
+
+          _this15._emitLine(';');
+        }
+      } else {
+        _this15._emit(_this15.buffer + " += runtime.suppressValue(");
+
+        if (_this15.throwOnUndefined) {
+          _this15._emit('runtime.ensureDefined(');
+        }
+
+        _this15.compile(child, frame);
+
+        if (_this15.throwOnUndefined) {
+          _this15._emit("," + node.lineno + "," + node.colno + ")");
+        }
+
+        _this15._emit(', env.opts.autoescape);\n');
+      }
+    });
+  };
+
+  _proto.compileRoot = function compileRoot(node, frame) {
+    var _this16 = this;
+
+    if (frame) {
+      this.fail('compileRoot: root node can\'t have frame');
+    }
+
+    frame = new Frame();
+
+    this._emitFuncBegin(node, 'root');
+
+    this._emitLine('var parentTemplate = null;');
+
+    this._compileChildren(node, frame);
+
+    this._emitLine('if(parentTemplate) {');
+
+    this._emitLine('parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);');
+
+    this._emitLine('} else {');
+
+    this._emitLine("cb(null, " + this.buffer + ");");
+
+    this._emitLine('}');
+
+    this._emitFuncEnd(true);
+
+    this.inBlock = true;
+    var blockNames = [];
+    var blocks = node.findAll(nodes.Block);
+    blocks.forEach(function (block, i) {
+      var name = block.name.value;
+
+      if (blockNames.indexOf(name) !== -1) {
+        throw new Error("Block \"" + name + "\" defined more than once.");
+      }
+
+      blockNames.push(name);
+
+      _this16._emitFuncBegin(block, "b_" + name);
+
+      var tmpFrame = new Frame();
+
+      _this16._emitLine('var frame = frame.push(true);');
+
+      _this16.compile(block.body, tmpFrame);
+
+      _this16._emitFuncEnd();
+    });
+
+    this._emitLine('return {');
+
+    blocks.forEach(function (block, i) {
+      var blockName = "b_" + block.name.value;
+
+      _this16._emitLine(blockName + ": " + blockName + ",");
+    });
+
+    this._emitLine('root: root\n};');
+  };
+
+  _proto.compile = function compile(node, frame) {
+    var _compile = this['compile' + node.typename];
+
+    if (_compile) {
+      _compile.call(this, node, frame);
+    } else {
+      this.fail("compile: Cannot compile node: " + node.typename, node.lineno, node.colno);
+    }
+  };
+
+  _proto.getCode = function getCode() {
+    return this.codebuf.join('');
+  };
+
+  return Compiler;
+}(Obj);
+
+module.exports = {
+  compile: function compile(src, asyncFilters, extensions, name, opts) {
+    if (opts === void 0) {
+      opts = {};
+    }
+
+    var c = new Compiler(name, opts.throwOnUndefined); // Run the extension preprocessors against the source.
+
+    var preprocessors = (extensions || []).map(function (ext) {
+      return ext.preprocess;
+    }).filter(function (f) {
+      return !!f;
+    });
+    var processedSrc = preprocessors.reduce(function (s, processor) {
+      return processor(s);
+    }, src);
+    c.compile(transformer.transform(parser.parse(processedSrc, extensions, opts), asyncFilters, name));
+    return c.getCode();
+  },
+  Compiler: Compiler
+};
+
+/***/ }),
+/* 6 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var path = __webpack_require__(4);
+
+var _require = __webpack_require__(1),
+    EmitterObj = _require.EmitterObj;
+
+module.exports =
+/*#__PURE__*/
+function (_EmitterObj) {
+  _inheritsLoose(Loader, _EmitterObj);
+
+  function Loader() {
+    return _EmitterObj.apply(this, arguments) || this;
+  }
+
+  var _proto = Loader.prototype;
+
+  _proto.resolve = function resolve(from, to) {
+    return path.resolve(path.dirname(from), to);
+  };
+
+  _proto.isRelative = function isRelative(filename) {
+    return filename.indexOf('./') === 0 || filename.indexOf('../') === 0;
+  };
+
+  return Loader;
+}(EmitterObj);
+
+/***/ }),
+/* 7 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var asap = __webpack_require__(12);
+
+var _waterfall = __webpack_require__(15);
+
+var lib = __webpack_require__(0);
+
+var compiler = __webpack_require__(5);
+
+var filters = __webpack_require__(18);
+
+var _require = __webpack_require__(10),
+    FileSystemLoader = _require.FileSystemLoader,
+    WebLoader = _require.WebLoader,
+    PrecompiledLoader = _require.PrecompiledLoader;
+
+var tests = __webpack_require__(20);
+
+var globals = __webpack_require__(21);
+
+var _require2 = __webpack_require__(1),
+    Obj = _require2.Obj,
+    EmitterObj = _require2.EmitterObj;
+
+var globalRuntime = __webpack_require__(2);
+
+var handleError = globalRuntime.handleError,
+    Frame = globalRuntime.Frame;
+
+var expressApp = __webpack_require__(22); // If the user is using the async API, *always* call it
+// asynchronously even if the template was synchronous.
+
+
+function callbackAsap(cb, err, res) {
+  asap(function () {
+    cb(err, res);
+  });
+}
+/**
+ * A no-op template, for use with {% include ignore missing %}
+ */
+
+
+var noopTmplSrc = {
+  type: 'code',
+  obj: {
+    root: function root(env, context, frame, runtime, cb) {
+      try {
+        cb(null, '');
+      } catch (e) {
+        cb(handleError(e, null, null));
+      }
+    }
+  }
+};
+
+var Environment =
+/*#__PURE__*/
+function (_EmitterObj) {
+  _inheritsLoose(Environment, _EmitterObj);
+
+  function Environment() {
+    return _EmitterObj.apply(this, arguments) || this;
+  }
+
+  var _proto = Environment.prototype;
+
+  _proto.init = function init(loaders, opts) {
+    var _this = this;
+
+    // The dev flag determines the trace that'll be shown on errors.
+    // If set to true, returns the full trace from the error point,
+    // otherwise will return trace starting from Template.render
+    // (the full trace from within nunjucks may confuse developers using
+    //  the library)
+    // defaults to false
+    opts = this.opts = opts || {};
+    this.opts.dev = !!opts.dev; // The autoescape flag sets global autoescaping. If true,
+    // every string variable will be escaped by default.
+    // If false, strings can be manually escaped using the `escape` filter.
+    // defaults to true
+
+    this.opts.autoescape = opts.autoescape != null ? opts.autoescape : true; // If true, this will make the system throw errors if trying
+    // to output a null or undefined value
+
+    this.opts.throwOnUndefined = !!opts.throwOnUndefined;
+    this.opts.trimBlocks = !!opts.trimBlocks;
+    this.opts.lstripBlocks = !!opts.lstripBlocks;
+    this.loaders = [];
+
+    if (!loaders) {
+      // The filesystem loader is only available server-side
+      if (FileSystemLoader) {
+        this.loaders = [new FileSystemLoader('views')];
+      } else if (WebLoader) {
+        this.loaders = [new WebLoader('/views')];
+      }
+    } else {
+      this.loaders = lib.isArray(loaders) ? loaders : [loaders];
+    } // It's easy to use precompiled templates: just include them
+    // before you configure nunjucks and this will automatically
+    // pick it up and use it
+
+
+    if (typeof window !== 'undefined' && window.nunjucksPrecompiled) {
+      this.loaders.unshift(new PrecompiledLoader(window.nunjucksPrecompiled));
+    }
+
+    this._initLoaders();
+
+    this.globals = globals();
+    this.filters = {};
+    this.tests = {};
+    this.asyncFilters = [];
+    this.extensions = {};
+    this.extensionsList = [];
+
+    lib._entries(filters).forEach(function (_ref) {
+      var name = _ref[0],
+          filter = _ref[1];
+      return _this.addFilter(name, filter);
+    });
+
+    lib._entries(tests).forEach(function (_ref2) {
+      var name = _ref2[0],
+          test = _ref2[1];
+      return _this.addTest(name, test);
+    });
+  };
+
+  _proto._initLoaders = function _initLoaders() {
+    var _this2 = this;
+
+    this.loaders.forEach(function (loader) {
+      // Caching and cache busting
+      loader.cache = {};
+
+      if (typeof loader.on === 'function') {
+        loader.on('update', function (name, fullname) {
+          loader.cache[name] = null;
+
+          _this2.emit('update', name, fullname, loader);
+        });
+        loader.on('load', function (name, source) {
+          _this2.emit('load', name, source, loader);
+        });
+      }
+    });
+  };
+
+  _proto.invalidateCache = function invalidateCache() {
+    this.loaders.forEach(function (loader) {
+      loader.cache = {};
+    });
+  };
+
+  _proto.addExtension = function addExtension(name, extension) {
+    extension.__name = name;
+    this.extensions[name] = extension;
+    this.extensionsList.push(extension);
+    return this;
+  };
+
+  _proto.removeExtension = function removeExtension(name) {
+    var extension = this.getExtension(name);
+
+    if (!extension) {
+      return;
+    }
+
+    this.extensionsList = lib.without(this.extensionsList, extension);
+    delete this.extensions[name];
+  };
+
+  _proto.getExtension = function getExtension(name) {
+    return this.extensions[name];
+  };
+
+  _proto.hasExtension = function hasExtension(name) {
+    return !!this.extensions[name];
+  };
+
+  _proto.addGlobal = function addGlobal(name, value) {
+    this.globals[name] = value;
+    return this;
+  };
+
+  _proto.getGlobal = function getGlobal(name) {
+    if (typeof this.globals[name] === 'undefined') {
+      throw new Error('global not found: ' + name);
+    }
+
+    return this.globals[name];
+  };
+
+  _proto.addFilter = function addFilter(name, func, async) {
+    var wrapped = func;
+
+    if (async) {
+      this.asyncFilters.push(name);
+    }
+
+    this.filters[name] = wrapped;
+    return this;
+  };
+
+  _proto.getFilter = function getFilter(name) {
+    if (!this.filters[name]) {
+      throw new Error('filter not found: ' + name);
+    }
+
+    return this.filters[name];
+  };
+
+  _proto.addTest = function addTest(name, func) {
+    this.tests[name] = func;
+    return this;
+  };
+
+  _proto.getTest = function getTest(name) {
+    if (!this.tests[name]) {
+      throw new Error('test not found: ' + name);
+    }
+
+    return this.tests[name];
+  };
+
+  _proto.resolveTemplate = function resolveTemplate(loader, parentName, filename) {
+    var isRelative = loader.isRelative && parentName ? loader.isRelative(filename) : false;
+    return isRelative && loader.resolve ? loader.resolve(parentName, filename) : filename;
+  };
+
+  _proto.getTemplate = function getTemplate(name, eagerCompile, parentName, ignoreMissing, cb) {
+    var _this3 = this;
+
+    var that = this;
+    var tmpl = null;
+
+    if (name && name.raw) {
+      // this fixes autoescape for templates referenced in symbols
+      name = name.raw;
+    }
+
+    if (lib.isFunction(parentName)) {
+      cb = parentName;
+      parentName = null;
+      eagerCompile = eagerCompile || false;
+    }
+
+    if (lib.isFunction(eagerCompile)) {
+      cb = eagerCompile;
+      eagerCompile = false;
+    }
+
+    if (name instanceof Template) {
+      tmpl = name;
+    } else if (typeof name !== 'string') {
+      throw new Error('template names must be a string: ' + name);
+    } else {
+      for (var i = 0; i < this.loaders.length; i++) {
+        var loader = this.loaders[i];
+        tmpl = loader.cache[this.resolveTemplate(loader, parentName, name)];
+
+        if (tmpl) {
+          break;
+        }
+      }
+    }
+
+    if (tmpl) {
+      if (eagerCompile) {
+        tmpl.compile();
+      }
+
+      if (cb) {
+        cb(null, tmpl);
+        return undefined;
+      } else {
+        return tmpl;
+      }
+    }
+
+    var syncResult;
+
+    var createTemplate = function createTemplate(err, info) {
+      if (!info && !err && !ignoreMissing) {
+        err = new Error('template not found: ' + name);
+      }
+
+      if (err) {
+        if (cb) {
+          cb(err);
+          return;
+        } else {
+          throw err;
+        }
+      }
+
+      var newTmpl;
+
+      if (!info) {
+        newTmpl = new Template(noopTmplSrc, _this3, '', eagerCompile);
+      } else {
+        newTmpl = new Template(info.src, _this3, info.path, eagerCompile);
+
+        if (!info.noCache) {
+          info.loader.cache[name] = newTmpl;
+        }
+      }
+
+      if (cb) {
+        cb(null, newTmpl);
+      } else {
+        syncResult = newTmpl;
+      }
+    };
+
+    lib.asyncIter(this.loaders, function (loader, i, next, done) {
+      function handle(err, src) {
+        if (err) {
+          done(err);
+        } else if (src) {
+          src.loader = loader;
+          done(null, src);
+        } else {
+          next();
+        }
+      } // Resolve name relative to parentName
+
+
+      name = that.resolveTemplate(loader, parentName, name);
+
+      if (loader.async) {
+        loader.getSource(name, handle);
+      } else {
+        handle(null, loader.getSource(name));
+      }
+    }, createTemplate);
+    return syncResult;
+  };
+
+  _proto.express = function express(app) {
+    return expressApp(this, app);
+  };
+
+  _proto.render = function render(name, ctx, cb) {
+    if (lib.isFunction(ctx)) {
+      cb = ctx;
+      ctx = null;
+    } // We support a synchronous API to make it easier to migrate
+    // existing code to async. This works because if you don't do
+    // anything async work, the whole thing is actually run
+    // synchronously.
+
+
+    var syncResult = null;
+    this.getTemplate(name, function (err, tmpl) {
+      if (err && cb) {
+        callbackAsap(cb, err);
+      } else if (err) {
+        throw err;
+      } else {
+        syncResult = tmpl.render(ctx, cb);
+      }
+    });
+    return syncResult;
+  };
+
+  _proto.renderString = function renderString(src, ctx, opts, cb) {
+    if (lib.isFunction(opts)) {
+      cb = opts;
+      opts = {};
+    }
+
+    opts = opts || {};
+    var tmpl = new Template(src, this, opts.path);
+    return tmpl.render(ctx, cb);
+  };
+
+  _proto.waterfall = function waterfall(tasks, callback, forceAsync) {
+    return _waterfall(tasks, callback, forceAsync);
+  };
+
+  return Environment;
+}(EmitterObj);
+
+var Context =
+/*#__PURE__*/
+function (_Obj) {
+  _inheritsLoose(Context, _Obj);
+
+  function Context() {
+    return _Obj.apply(this, arguments) || this;
+  }
+
+  var _proto2 = Context.prototype;
+
+  _proto2.init = function init(ctx, blocks, env) {
+    var _this4 = this;
+
+    // Has to be tied to an environment so we can tap into its globals.
+    this.env = env || new Environment(); // Make a duplicate of ctx
+
+    this.ctx = lib.extend({}, ctx);
+    this.blocks = {};
+    this.exported = [];
+    lib.keys(blocks).forEach(function (name) {
+      _this4.addBlock(name, blocks[name]);
+    });
+  };
+
+  _proto2.lookup = function lookup(name) {
+    // This is one of the most called functions, so optimize for
+    // the typical case where the name isn't in the globals
+    if (name in this.env.globals && !(name in this.ctx)) {
+      return this.env.globals[name];
+    } else {
+      return this.ctx[name];
+    }
+  };
+
+  _proto2.setVariable = function setVariable(name, val) {
+    this.ctx[name] = val;
+  };
+
+  _proto2.getVariables = function getVariables() {
+    return this.ctx;
+  };
+
+  _proto2.addBlock = function addBlock(name, block) {
+    this.blocks[name] = this.blocks[name] || [];
+    this.blocks[name].push(block);
+    return this;
+  };
+
+  _proto2.getBlock = function getBlock(name) {
+    if (!this.blocks[name]) {
+      throw new Error('unknown block "' + name + '"');
+    }
+
+    return this.blocks[name][0];
+  };
+
+  _proto2.getSuper = function getSuper(env, name, block, frame, runtime, cb) {
+    var idx = lib.indexOf(this.blocks[name] || [], block);
+    var blk = this.blocks[name][idx + 1];
+    var context = this;
+
+    if (idx === -1 || !blk) {
+      throw new Error('no super block available for "' + name + '"');
+    }
+
+    blk(env, context, frame, runtime, cb);
+  };
+
+  _proto2.addExport = function addExport(name) {
+    this.exported.push(name);
+  };
+
+  _proto2.getExported = function getExported() {
+    var _this5 = this;
+
+    var exported = {};
+    this.exported.forEach(function (name) {
+      exported[name] = _this5.ctx[name];
+    });
+    return exported;
+  };
+
+  return Context;
+}(Obj);
+
+var Template =
+/*#__PURE__*/
+function (_Obj2) {
+  _inheritsLoose(Template, _Obj2);
+
+  function Template() {
+    return _Obj2.apply(this, arguments) || this;
+  }
+
+  var _proto3 = Template.prototype;
+
+  _proto3.init = function init(src, env, path, eagerCompile) {
+    this.env = env || new Environment();
+
+    if (lib.isObject(src)) {
+      switch (src.type) {
+        case 'code':
+          this.tmplProps = src.obj;
+          break;
+
+        case 'string':
+          this.tmplStr = src.obj;
+          break;
+
+        default:
+          throw new Error("Unexpected template object type " + src.type + "; expected 'code', or 'string'");
+      }
+    } else if (lib.isString(src)) {
+      this.tmplStr = src;
+    } else {
+      throw new Error('src must be a string or an object describing the source');
+    }
+
+    this.path = path;
+
+    if (eagerCompile) {
+      try {
+        this._compile();
+      } catch (err) {
+        throw lib._prettifyError(this.path, this.env.opts.dev, err);
+      }
+    } else {
+      this.compiled = false;
+    }
+  };
+
+  _proto3.render = function render(ctx, parentFrame, cb) {
+    var _this6 = this;
+
+    if (typeof ctx === 'function') {
+      cb = ctx;
+      ctx = {};
+    } else if (typeof parentFrame === 'function') {
+      cb = parentFrame;
+      parentFrame = null;
+    } // If there is a parent frame, we are being called from internal
+    // code of another template, and the internal system
+    // depends on the sync/async nature of the parent template
+    // to be inherited, so force an async callback
+
+
+    var forceAsync = !parentFrame; // Catch compile errors for async rendering
+
+    try {
+      this.compile();
+    } catch (e) {
+      var err = lib._prettifyError(this.path, this.env.opts.dev, e);
+
+      if (cb) {
+        return callbackAsap(cb, err);
+      } else {
+        throw err;
+      }
+    }
+
+    var context = new Context(ctx || {}, this.blocks, this.env);
+    var frame = parentFrame ? parentFrame.push(true) : new Frame();
+    frame.topLevel = true;
+    var syncResult = null;
+    var didError = false;
+    this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err, res) {
+      // TODO: this is actually a bug in the compiled template (because waterfall
+      // tasks are both not passing errors up the chain of callbacks AND are not
+      // causing a return from the top-most render function). But fixing that
+      // will require a more substantial change to the compiler.
+      if (didError && cb && typeof res !== 'undefined') {
+        // prevent multiple calls to cb
+        return;
+      }
+
+      if (err) {
+        err = lib._prettifyError(_this6.path, _this6.env.opts.dev, err);
+        didError = true;
+      }
+
+      if (cb) {
+        if (forceAsync) {
+          callbackAsap(cb, err, res);
+        } else {
+          cb(err, res);
+        }
+      } else {
+        if (err) {
+          throw err;
+        }
+
+        syncResult = res;
+      }
+    });
+    return syncResult;
+  };
+
+  _proto3.getExported = function getExported(ctx, parentFrame, cb) {
+    // eslint-disable-line consistent-return
+    if (typeof ctx === 'function') {
+      cb = ctx;
+      ctx = {};
+    }
+
+    if (typeof parentFrame === 'function') {
+      cb = parentFrame;
+      parentFrame = null;
+    } // Catch compile errors for async rendering
+
+
+    try {
+      this.compile();
+    } catch (e) {
+      if (cb) {
+        return cb(e);
+      } else {
+        throw e;
+      }
+    }
+
+    var frame = parentFrame ? parentFrame.push() : new Frame();
+    frame.topLevel = true; // Run the rootRenderFunc to populate the context with exported vars
+
+    var context = new Context(ctx || {}, this.blocks, this.env);
+    this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err) {
+      if (err) {
+        cb(err, null);
+      } else {
+        cb(null, context.getExported());
+      }
+    });
+  };
+
+  _proto3.compile = function compile() {
+    if (!this.compiled) {
+      this._compile();
+    }
+  };
+
+  _proto3._compile = function _compile() {
+    var props;
+
+    if (this.tmplProps) {
+      props = this.tmplProps;
+    } else {
+      var source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, this.path, this.env.opts);
+      var func = new Function(source); // eslint-disable-line no-new-func
+
+      props = func();
+    }
+
+    this.blocks = this._getBlocks(props);
+    this.rootRenderFunc = props.root;
+    this.compiled = true;
+  };
+
+  _proto3._getBlocks = function _getBlocks(props) {
+    var blocks = {};
+    lib.keys(props).forEach(function (k) {
+      if (k.slice(0, 2) === 'b_') {
+        blocks[k.slice(2)] = props[k];
+      }
+    });
+    return blocks;
+  };
+
+  return Template;
+}(Obj);
+
+module.exports = {
+  Environment: Environment,
+  Template: Template
+};
+
+/***/ }),
+/* 8 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var lexer = __webpack_require__(9);
+
+var nodes = __webpack_require__(3);
+
+var Obj = __webpack_require__(1).Obj;
+
+var lib = __webpack_require__(0);
+
+var Parser =
+/*#__PURE__*/
+function (_Obj) {
+  _inheritsLoose(Parser, _Obj);
+
+  function Parser() {
+    return _Obj.apply(this, arguments) || this;
+  }
+
+  var _proto = Parser.prototype;
+
+  _proto.init = function init(tokens) {
+    this.tokens = tokens;
+    this.peeked = null;
+    this.breakOnBlocks = null;
+    this.dropLeadingWhitespace = false;
+    this.extensions = [];
+  };
+
+  _proto.nextToken = function nextToken(withWhitespace) {
+    var tok;
+
+    if (this.peeked) {
+      if (!withWhitespace && this.peeked.type === lexer.TOKEN_WHITESPACE) {
+        this.peeked = null;
+      } else {
+        tok = this.peeked;
+        this.peeked = null;
+        return tok;
+      }
+    }
+
+    tok = this.tokens.nextToken();
+
+    if (!withWhitespace) {
+      while (tok && tok.type === lexer.TOKEN_WHITESPACE) {
+        tok = this.tokens.nextToken();
+      }
+    }
+
+    return tok;
+  };
+
+  _proto.peekToken = function peekToken() {
+    this.peeked = this.peeked || this.nextToken();
+    return this.peeked;
+  };
+
+  _proto.pushToken = function pushToken(tok) {
+    if (this.peeked) {
+      throw new Error('pushToken: can only push one token on between reads');
+    }
+
+    this.peeked = tok;
+  };
+
+  _proto.error = function error(msg, lineno, colno) {
+    if (lineno === undefined || colno === undefined) {
+      var tok = this.peekToken() || {};
+      lineno = tok.lineno;
+      colno = tok.colno;
+    }
+
+    if (lineno !== undefined) {
+      lineno += 1;
+    }
+
+    if (colno !== undefined) {
+      colno += 1;
+    }
+
+    return new lib.TemplateError(msg, lineno, colno);
+  };
+
+  _proto.fail = function fail(msg, lineno, colno) {
+    throw this.error(msg, lineno, colno);
+  };
+
+  _proto.skip = function skip(type) {
+    var tok = this.nextToken();
+
+    if (!tok || tok.type !== type) {
+      this.pushToken(tok);
+      return false;
+    }
+
+    return true;
+  };
+
+  _proto.expect = function expect(type) {
+    var tok = this.nextToken();
+
+    if (tok.type !== type) {
+      this.fail('expected ' + type + ', got ' + tok.type, tok.lineno, tok.colno);
+    }
+
+    return tok;
+  };
+
+  _proto.skipValue = function skipValue(type, val) {
+    var tok = this.nextToken();
+
+    if (!tok || tok.type !== type || tok.value !== val) {
+      this.pushToken(tok);
+      return false;
+    }
+
+    return true;
+  };
+
+  _proto.skipSymbol = function skipSymbol(val) {
+    return this.skipValue(lexer.TOKEN_SYMBOL, val);
+  };
+
+  _proto.advanceAfterBlockEnd = function advanceAfterBlockEnd(name) {
+    var tok;
+
+    if (!name) {
+      tok = this.peekToken();
+
+      if (!tok) {
+        this.fail('unexpected end of file');
+      }
+
+      if (tok.type !== lexer.TOKEN_SYMBOL) {
+        this.fail('advanceAfterBlockEnd: expected symbol token or ' + 'explicit name to be passed');
+      }
+
+      name = this.nextToken().value;
+    }
+
+    tok = this.nextToken();
+
+    if (tok && tok.type === lexer.TOKEN_BLOCK_END) {
+      if (tok.value.charAt(0) === '-') {
+        this.dropLeadingWhitespace = true;
+      }
+    } else {
+      this.fail('expected block end in ' + name + ' statement');
+    }
+
+    return tok;
+  };
+
+  _proto.advanceAfterVariableEnd = function advanceAfterVariableEnd() {
+    var tok = this.nextToken();
+
+    if (tok && tok.type === lexer.TOKEN_VARIABLE_END) {
+      this.dropLeadingWhitespace = tok.value.charAt(tok.value.length - this.tokens.tags.VARIABLE_END.length - 1) === '-';
+    } else {
+      this.pushToken(tok);
+      this.fail('expected variable end');
+    }
+  };
+
+  _proto.parseFor = function parseFor() {
+    var forTok = this.peekToken();
+    var node;
+    var endBlock;
+
+    if (this.skipSymbol('for')) {
+      node = new nodes.For(forTok.lineno, forTok.colno);
+      endBlock = 'endfor';
+    } else if (this.skipSymbol('asyncEach')) {
+      node = new nodes.AsyncEach(forTok.lineno, forTok.colno);
+      endBlock = 'endeach';
+    } else if (this.skipSymbol('asyncAll')) {
+      node = new nodes.AsyncAll(forTok.lineno, forTok.colno);
+      endBlock = 'endall';
+    } else {
+      this.fail('parseFor: expected for{Async}', forTok.lineno, forTok.colno);
+    }
+
+    node.name = this.parsePrimary();
+
+    if (!(node.name instanceof nodes.Symbol)) {
+      this.fail('parseFor: variable name expected for loop');
+    }
+
+    var type = this.peekToken().type;
+
+    if (type === lexer.TOKEN_COMMA) {
+      // key/value iteration
+      var key = node.name;
+      node.name = new nodes.Array(key.lineno, key.colno);
+      node.name.addChild(key);
+
+      while (this.skip(lexer.TOKEN_COMMA)) {
+        var prim = this.parsePrimary();
+        node.name.addChild(prim);
+      }
+    }
+
+    if (!this.skipSymbol('in')) {
+      this.fail('parseFor: expected "in" keyword for loop', forTok.lineno, forTok.colno);
+    }
+
+    node.arr = this.parseExpression();
+    this.advanceAfterBlockEnd(forTok.value);
+    node.body = this.parseUntilBlocks(endBlock, 'else');
+
+    if (this.skipSymbol('else')) {
+      this.advanceAfterBlockEnd('else');
+      node.else_ = this.parseUntilBlocks(endBlock);
+    }
+
+    this.advanceAfterBlockEnd();
+    return node;
+  };
+
+  _proto.parseMacro = function parseMacro() {
+    var macroTok = this.peekToken();
+
+    if (!this.skipSymbol('macro')) {
+      this.fail('expected macro');
+    }
+
+    var name = this.parsePrimary(true);
+    var args = this.parseSignature();
+    var node = new nodes.Macro(macroTok.lineno, macroTok.colno, name, args);
+    this.advanceAfterBlockEnd(macroTok.value);
+    node.body = this.parseUntilBlocks('endmacro');
+    this.advanceAfterBlockEnd();
+    return node;
+  };
+
+  _proto.parseCall = function parseCall() {
+    // a call block is parsed as a normal FunCall, but with an added
+    // 'caller' kwarg which is a Caller node.
+    var callTok = this.peekToken();
+
+    if (!this.skipSymbol('call')) {
+      this.fail('expected call');
+    }
+
+    var callerArgs = this.parseSignature(true) || new nodes.NodeList();
+    var macroCall = this.parsePrimary();
+    this.advanceAfterBlockEnd(callTok.value);
+    var body = this.parseUntilBlocks('endcall');
+    this.advanceAfterBlockEnd();
+    var callerName = new nodes.Symbol(callTok.lineno, callTok.colno, 'caller');
+    var callerNode = new nodes.Caller(callTok.lineno, callTok.colno, callerName, callerArgs, body); // add the additional caller kwarg, adding kwargs if necessary
+
+    var args = macroCall.args.children;
+
+    if (!(args[args.length - 1] instanceof nodes.KeywordArgs)) {
+      args.push(new nodes.KeywordArgs());
+    }
+
+    var kwargs = args[args.length - 1];
+    kwargs.addChild(new nodes.Pair(callTok.lineno, callTok.colno, callerName, callerNode));
+    return new nodes.Output(callTok.lineno, callTok.colno, [macroCall]);
+  };
+
+  _proto.parseWithContext = function parseWithContext() {
+    var tok = this.peekToken();
+    var withContext = null;
+
+    if (this.skipSymbol('with')) {
+      withContext = true;
+    } else if (this.skipSymbol('without')) {
+      withContext = false;
+    }
+
+    if (withContext !== null) {
+      if (!this.skipSymbol('context')) {
+        this.fail('parseFrom: expected context after with/without', tok.lineno, tok.colno);
+      }
+    }
+
+    return withContext;
+  };
+
+  _proto.parseImport = function parseImport() {
+    var importTok = this.peekToken();
+
+    if (!this.skipSymbol('import')) {
+      this.fail('parseImport: expected import', importTok.lineno, importTok.colno);
+    }
+
+    var template = this.parseExpression();
+
+    if (!this.skipSymbol('as')) {
+      this.fail('parseImport: expected "as" keyword', importTok.lineno, importTok.colno);
+    }
+
+    var target = this.parseExpression();
+    var withContext = this.parseWithContext();
+    var node = new nodes.Import(importTok.lineno, importTok.colno, template, target, withContext);
+    this.advanceAfterBlockEnd(importTok.value);
+    return node;
+  };
+
+  _proto.parseFrom = function parseFrom() {
+    var fromTok = this.peekToken();
+
+    if (!this.skipSymbol('from')) {
+      this.fail('parseFrom: expected from');
+    }
+
+    var template = this.parseExpression();
+
+    if (!this.skipSymbol('import')) {
+      this.fail('parseFrom: expected import', fromTok.lineno, fromTok.colno);
+    }
+
+    var names = new nodes.NodeList();
+    var withContext;
+
+    while (1) {
+      // eslint-disable-line no-constant-condition
+      var nextTok = this.peekToken();
+
+      if (nextTok.type === lexer.TOKEN_BLOCK_END) {
+        if (!names.children.length) {
+          this.fail('parseFrom: Expected at least one import name', fromTok.lineno, fromTok.colno);
+        } // Since we are manually advancing past the block end,
+        // need to keep track of whitespace control (normally
+        // this is done in `advanceAfterBlockEnd`
+
+
+        if (nextTok.value.charAt(0) === '-') {
+          this.dropLeadingWhitespace = true;
+        }
+
+        this.nextToken();
+        break;
+      }
+
+      if (names.children.length > 0 && !this.skip(lexer.TOKEN_COMMA)) {
+        this.fail('parseFrom: expected comma', fromTok.lineno, fromTok.colno);
+      }
+
+      var name = this.parsePrimary();
+
+      if (name.value.charAt(0) === '_') {
+        this.fail('parseFrom: names starting with an underscore cannot be imported', name.lineno, name.colno);
+      }
+
+      if (this.skipSymbol('as')) {
+        var alias = this.parsePrimary();
+        names.addChild(new nodes.Pair(name.lineno, name.colno, name, alias));
+      } else {
+        names.addChild(name);
+      }
+
+      withContext = this.parseWithContext();
+    }
+
+    return new nodes.FromImport(fromTok.lineno, fromTok.colno, template, names, withContext);
+  };
+
+  _proto.parseBlock = function parseBlock() {
+    var tag = this.peekToken();
+
+    if (!this.skipSymbol('block')) {
+      this.fail('parseBlock: expected block', tag.lineno, tag.colno);
+    }
+
+    var node = new nodes.Block(tag.lineno, tag.colno);
+    node.name = this.parsePrimary();
+
+    if (!(node.name instanceof nodes.Symbol)) {
+      this.fail('parseBlock: variable name expected', tag.lineno, tag.colno);
+    }
+
+    this.advanceAfterBlockEnd(tag.value);
+    node.body = this.parseUntilBlocks('endblock');
+    this.skipSymbol('endblock');
+    this.skipSymbol(node.name.value);
+    var tok = this.peekToken();
+
+    if (!tok) {
+      this.fail('parseBlock: expected endblock, got end of file');
+    }
+
+    this.advanceAfterBlockEnd(tok.value);
+    return node;
+  };
+
+  _proto.parseExtends = function parseExtends() {
+    var tagName = 'extends';
+    var tag = this.peekToken();
+
+    if (!this.skipSymbol(tagName)) {
+      this.fail('parseTemplateRef: expected ' + tagName);
+    }
+
+    var node = new nodes.Extends(tag.lineno, tag.colno);
+    node.template = this.parseExpression();
+    this.advanceAfterBlockEnd(tag.value);
+    return node;
+  };
+
+  _proto.parseInclude = function parseInclude() {
+    var tagName = 'include';
+    var tag = this.peekToken();
+
+    if (!this.skipSymbol(tagName)) {
+      this.fail('parseInclude: expected ' + tagName);
+    }
+
+    var node = new nodes.Include(tag.lineno, tag.colno);
+    node.template = this.parseExpression();
+
+    if (this.skipSymbol('ignore') && this.skipSymbol('missing')) {
+      node.ignoreMissing = true;
+    }
+
+    this.advanceAfterBlockEnd(tag.value);
+    return node;
+  };
+
+  _proto.parseIf = function parseIf() {
+    var tag = this.peekToken();
+    var node;
+
+    if (this.skipSymbol('if') || this.skipSymbol('elif') || this.skipSymbol('elseif')) {
+      node = new nodes.If(tag.lineno, tag.colno);
+    } else if (this.skipSymbol('ifAsync')) {
+      node = new nodes.IfAsync(tag.lineno, tag.colno);
+    } else {
+      this.fail('parseIf: expected if, elif, or elseif', tag.lineno, tag.colno);
+    }
+
+    node.cond = this.parseExpression();
+    this.advanceAfterBlockEnd(tag.value);
+    node.body = this.parseUntilBlocks('elif', 'elseif', 'else', 'endif');
+    var tok = this.peekToken();
+
+    switch (tok && tok.value) {
+      case 'elseif':
+      case 'elif':
+        node.else_ = this.parseIf();
+        break;
+
+      case 'else':
+        this.advanceAfterBlockEnd();
+        node.else_ = this.parseUntilBlocks('endif');
+        this.advanceAfterBlockEnd();
+        break;
+
+      case 'endif':
+        node.else_ = null;
+        this.advanceAfterBlockEnd();
+        break;
+
+      default:
+        this.fail('parseIf: expected elif, else, or endif, got end of file');
+    }
+
+    return node;
+  };
+
+  _proto.parseSet = function parseSet() {
+    var tag = this.peekToken();
+
+    if (!this.skipSymbol('set')) {
+      this.fail('parseSet: expected set', tag.lineno, tag.colno);
+    }
+
+    var node = new nodes.Set(tag.lineno, tag.colno, []);
+    var target;
+
+    while (target = this.parsePrimary()) {
+      node.targets.push(target);
+
+      if (!this.skip(lexer.TOKEN_COMMA)) {
+        break;
+      }
+    }
+
+    if (!this.skipValue(lexer.TOKEN_OPERATOR, '=')) {
+      if (!this.skip(lexer.TOKEN_BLOCK_END)) {
+        this.fail('parseSet: expected = or block end in set tag', tag.lineno, tag.colno);
+      } else {
+        node.body = new nodes.Capture(tag.lineno, tag.colno, this.parseUntilBlocks('endset'));
+        node.value = null;
+        this.advanceAfterBlockEnd();
+      }
+    } else {
+      node.value = this.parseExpression();
+      this.advanceAfterBlockEnd(tag.value);
+    }
+
+    return node;
+  };
+
+  _proto.parseSwitch = function parseSwitch() {
+    /*
+     * Store the tag names in variables in case someone ever wants to
+     * customize this.
+     */
+    var switchStart = 'switch';
+    var switchEnd = 'endswitch';
+    var caseStart = 'case';
+    var caseDefault = 'default'; // Get the switch tag.
+
+    var tag = this.peekToken(); // fail early if we get some unexpected tag.
+
+    if (!this.skipSymbol(switchStart) && !this.skipSymbol(caseStart) && !this.skipSymbol(caseDefault)) {
+      this.fail('parseSwitch: expected "switch," "case" or "default"', tag.lineno, tag.colno);
+    } // parse the switch expression
+
+
+    var expr = this.parseExpression(); // advance until a start of a case, a default case or an endswitch.
+
+    this.advanceAfterBlockEnd(switchStart);
+    this.parseUntilBlocks(caseStart, caseDefault, switchEnd); // this is the first case. it could also be an endswitch, we'll check.
+
+    var tok = this.peekToken(); // create new variables for our cases and default case.
+
+    var cases = [];
+    var defaultCase; // while we're dealing with new cases nodes...
+
+    do {
+      // skip the start symbol and get the case expression
+      this.skipSymbol(caseStart);
+      var cond = this.parseExpression();
+      this.advanceAfterBlockEnd(switchStart); // get the body of the case node and add it to the array of cases.
+
+      var body = this.parseUntilBlocks(caseStart, caseDefault, switchEnd);
+      cases.push(new nodes.Case(tok.line, tok.col, cond, body)); // get our next case
+
+      tok = this.peekToken();
+    } while (tok && tok.value === caseStart); // we either have a default case or a switch end.
+
+
+    switch (tok.value) {
+      case caseDefault:
+        this.advanceAfterBlockEnd();
+        defaultCase = this.parseUntilBlocks(switchEnd);
+        this.advanceAfterBlockEnd();
+        break;
+
+      case switchEnd:
+        this.advanceAfterBlockEnd();
+        break;
+
+      default:
+        // otherwise bail because EOF
+        this.fail('parseSwitch: expected "case," "default" or "endswitch," got EOF.');
+    } // and return the switch node.
+
+
+    return new nodes.Switch(tag.lineno, tag.colno, expr, cases, defaultCase);
+  };
+
+  _proto.parseStatement = function parseStatement() {
+    var tok = this.peekToken();
+    var node;
+
+    if (tok.type !== lexer.TOKEN_SYMBOL) {
+      this.fail('tag name expected', tok.lineno, tok.colno);
+    }
+
+    if (this.breakOnBlocks && lib.indexOf(this.breakOnBlocks, tok.value) !== -1) {
+      return null;
+    }
+
+    switch (tok.value) {
+      case 'raw':
+        return this.parseRaw();
+
+      case 'verbatim':
+        return this.parseRaw('verbatim');
+
+      case 'if':
+      case 'ifAsync':
+        return this.parseIf();
+
+      case 'for':
+      case 'asyncEach':
+      case 'asyncAll':
+        return this.parseFor();
+
+      case 'block':
+        return this.parseBlock();
+
+      case 'extends':
+        return this.parseExtends();
+
+      case 'include':
+        return this.parseInclude();
+
+      case 'set':
+        return this.parseSet();
+
+      case 'macro':
+        return this.parseMacro();
+
+      case 'call':
+        return this.parseCall();
+
+      case 'import':
+        return this.parseImport();
+
+      case 'from':
+        return this.parseFrom();
+
+      case 'filter':
+        return this.parseFilterStatement();
+
+      case 'switch':
+        return this.parseSwitch();
+
+      default:
+        if (this.extensions.length) {
+          for (var i = 0; i < this.extensions.length; i++) {
+            var ext = this.extensions[i];
+
+            if (lib.indexOf(ext.tags || [], tok.value) !== -1) {
+              return ext.parse(this, nodes, lexer);
+            }
+          }
+        }
+
+        this.fail('unknown block tag: ' + tok.value, tok.lineno, tok.colno);
+    }
+
+    return node;
+  };
+
+  _proto.parseRaw = function parseRaw(tagName) {
+    tagName = tagName || 'raw';
+    var endTagName = 'end' + tagName; // Look for upcoming raw blocks (ignore all other kinds of blocks)
+
+    var rawBlockRegex = new RegExp('([\\s\\S]*?){%\\s*(' + tagName + '|' + endTagName + ')\\s*(?=%})%}');
+    var rawLevel = 1;
+    var str = '';
+    var matches = null; // Skip opening raw token
+    // Keep this token to track line and column numbers
+
+    var begun = this.advanceAfterBlockEnd(); // Exit when there's nothing to match
+    // or when we've found the matching "endraw" block
+
+    while ((matches = this.tokens._extractRegex(rawBlockRegex)) && rawLevel > 0) {
+      var all = matches[0];
+      var pre = matches[1];
+      var blockName = matches[2]; // Adjust rawlevel
+
+      if (blockName === tagName) {
+        rawLevel += 1;
+      } else if (blockName === endTagName) {
+        rawLevel -= 1;
+      } // Add to str
+
+
+      if (rawLevel === 0) {
+        // We want to exclude the last "endraw"
+        str += pre; // Move tokenizer to beginning of endraw block
+
+        this.tokens.backN(all.length - pre.length);
+      } else {
+        str += all;
+      }
+    }
+
+    return new nodes.Output(begun.lineno, begun.colno, [new nodes.TemplateData(begun.lineno, begun.colno, str)]);
+  };
+
+  _proto.parsePostfix = function parsePostfix(node) {
+    var lookup;
+    var tok = this.peekToken();
+
+    while (tok) {
+      if (tok.type === lexer.TOKEN_LEFT_PAREN) {
+        // Function call
+        node = new nodes.FunCall(tok.lineno, tok.colno, node, this.parseSignature());
+      } else if (tok.type === lexer.TOKEN_LEFT_BRACKET) {
+        // Reference
+        lookup = this.parseAggregate();
+
+        if (lookup.children.length > 1) {
+          this.fail('invalid index');
+        }
+
+        node = new nodes.LookupVal(tok.lineno, tok.colno, node, lookup.children[0]);
+      } else if (tok.type === lexer.TOKEN_OPERATOR && tok.value === '.') {
+        // Reference
+        this.nextToken();
+        var val = this.nextToken();
+
+        if (val.type !== lexer.TOKEN_SYMBOL) {
+          this.fail('expected name as lookup value, got ' + val.value, val.lineno, val.colno);
+        } // Make a literal string because it's not a variable
+        // reference
+
+
+        lookup = new nodes.Literal(val.lineno, val.colno, val.value);
+        node = new nodes.LookupVal(tok.lineno, tok.colno, node, lookup);
+      } else {
+        break;
+      }
+
+      tok = this.peekToken();
+    }
+
+    return node;
+  };
+
+  _proto.parseExpression = function parseExpression() {
+    var node = this.parseInlineIf();
+    return node;
+  };
+
+  _proto.parseInlineIf = function parseInlineIf() {
+    var node = this.parseOr();
+
+    if (this.skipSymbol('if')) {
+      var condNode = this.parseOr();
+      var bodyNode = node;
+      node = new nodes.InlineIf(node.lineno, node.colno);
+      node.body = bodyNode;
+      node.cond = condNode;
+
+      if (this.skipSymbol('else')) {
+        node.else_ = this.parseOr();
+      } else {
+        node.else_ = null;
+      }
+    }
+
+    return node;
+  };
+
+  _proto.parseOr = function parseOr() {
+    var node = this.parseAnd();
+
+    while (this.skipSymbol('or')) {
+      var node2 = this.parseAnd();
+      node = new nodes.Or(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseAnd = function parseAnd() {
+    var node = this.parseNot();
+
+    while (this.skipSymbol('and')) {
+      var node2 = this.parseNot();
+      node = new nodes.And(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseNot = function parseNot() {
+    var tok = this.peekToken();
+
+    if (this.skipSymbol('not')) {
+      return new nodes.Not(tok.lineno, tok.colno, this.parseNot());
+    }
+
+    return this.parseIn();
+  };
+
+  _proto.parseIn = function parseIn() {
+    var node = this.parseIs();
+
+    while (1) {
+      // eslint-disable-line no-constant-condition
+      // check if the next token is 'not'
+      var tok = this.nextToken();
+
+      if (!tok) {
+        break;
+      }
+
+      var invert = tok.type === lexer.TOKEN_SYMBOL && tok.value === 'not'; // if it wasn't 'not', put it back
+
+      if (!invert) {
+        this.pushToken(tok);
+      }
+
+      if (this.skipSymbol('in')) {
+        var node2 = this.parseIs();
+        node = new nodes.In(node.lineno, node.colno, node, node2);
+
+        if (invert) {
+          node = new nodes.Not(node.lineno, node.colno, node);
+        }
+      } else {
+        // if we'd found a 'not' but this wasn't an 'in', put back the 'not'
+        if (invert) {
+          this.pushToken(tok);
+        }
+
+        break;
+      }
+    }
+
+    return node;
+  }; // I put this right after "in" in the operator precedence stack. That can
+  // obviously be changed to be closer to Jinja.
+
+
+  _proto.parseIs = function parseIs() {
+    var node = this.parseCompare(); // look for an is
+
+    if (this.skipSymbol('is')) {
+      // look for a not
+      var not = this.skipSymbol('not'); // get the next node
+
+      var node2 = this.parseCompare(); // create an Is node using the next node and the info from our Is node.
+
+      node = new nodes.Is(node.lineno, node.colno, node, node2); // if we have a Not, create a Not node from our Is node.
+
+      if (not) {
+        node = new nodes.Not(node.lineno, node.colno, node);
+      }
+    } // return the node.
+
+
+    return node;
+  };
+
+  _proto.parseCompare = function parseCompare() {
+    var compareOps = ['==', '===', '!=', '!==', '<', '>', '<=', '>='];
+    var expr = this.parseConcat();
+    var ops = [];
+
+    while (1) {
+      // eslint-disable-line no-constant-condition
+      var tok = this.nextToken();
+
+      if (!tok) {
+        break;
+      } else if (compareOps.indexOf(tok.value) !== -1) {
+        ops.push(new nodes.CompareOperand(tok.lineno, tok.colno, this.parseConcat(), tok.value));
+      } else {
+        this.pushToken(tok);
+        break;
+      }
+    }
+
+    if (ops.length) {
+      return new nodes.Compare(ops[0].lineno, ops[0].colno, expr, ops);
+    } else {
+      return expr;
+    }
+  }; // finds the '~' for string concatenation
+
+
+  _proto.parseConcat = function parseConcat() {
+    var node = this.parseAdd();
+
+    while (this.skipValue(lexer.TOKEN_TILDE, '~')) {
+      var node2 = this.parseAdd();
+      node = new nodes.Concat(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseAdd = function parseAdd() {
+    var node = this.parseSub();
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '+')) {
+      var node2 = this.parseSub();
+      node = new nodes.Add(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseSub = function parseSub() {
+    var node = this.parseMul();
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '-')) {
+      var node2 = this.parseMul();
+      node = new nodes.Sub(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseMul = function parseMul() {
+    var node = this.parseDiv();
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '*')) {
+      var node2 = this.parseDiv();
+      node = new nodes.Mul(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseDiv = function parseDiv() {
+    var node = this.parseFloorDiv();
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '/')) {
+      var node2 = this.parseFloorDiv();
+      node = new nodes.Div(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseFloorDiv = function parseFloorDiv() {
+    var node = this.parseMod();
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '//')) {
+      var node2 = this.parseMod();
+      node = new nodes.FloorDiv(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseMod = function parseMod() {
+    var node = this.parsePow();
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '%')) {
+      var node2 = this.parsePow();
+      node = new nodes.Mod(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parsePow = function parsePow() {
+    var node = this.parseUnary();
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '**')) {
+      var node2 = this.parseUnary();
+      node = new nodes.Pow(node.lineno, node.colno, node, node2);
+    }
+
+    return node;
+  };
+
+  _proto.parseUnary = function parseUnary(noFilters) {
+    var tok = this.peekToken();
+    var node;
+
+    if (this.skipValue(lexer.TOKEN_OPERATOR, '-')) {
+      node = new nodes.Neg(tok.lineno, tok.colno, this.parseUnary(true));
+    } else if (this.skipValue(lexer.TOKEN_OPERATOR, '+')) {
+      node = new nodes.Pos(tok.lineno, tok.colno, this.parseUnary(true));
+    } else {
+      node = this.parsePrimary();
+    }
+
+    if (!noFilters) {
+      node = this.parseFilter(node);
+    }
+
+    return node;
+  };
+
+  _proto.parsePrimary = function parsePrimary(noPostfix) {
+    var tok = this.nextToken();
+    var val;
+    var node = null;
+
+    if (!tok) {
+      this.fail('expected expression, got end of file');
+    } else if (tok.type === lexer.TOKEN_STRING) {
+      val = tok.value;
+    } else if (tok.type === lexer.TOKEN_INT) {
+      val = parseInt(tok.value, 10);
+    } else if (tok.type === lexer.TOKEN_FLOAT) {
+      val = parseFloat(tok.value);
+    } else if (tok.type === lexer.TOKEN_BOOLEAN) {
+      if (tok.value === 'true') {
+        val = true;
+      } else if (tok.value === 'false') {
+        val = false;
+      } else {
+        this.fail('invalid boolean: ' + tok.value, tok.lineno, tok.colno);
+      }
+    } else if (tok.type === lexer.TOKEN_NONE) {
+      val = null;
+    } else if (tok.type === lexer.TOKEN_REGEX) {
+      val = new RegExp(tok.value.body, tok.value.flags);
+    }
+
+    if (val !== undefined) {
+      node = new nodes.Literal(tok.lineno, tok.colno, val);
+    } else if (tok.type === lexer.TOKEN_SYMBOL) {
+      node = new nodes.Symbol(tok.lineno, tok.colno, tok.value);
+    } else {
+      // See if it's an aggregate type, we need to push the
+      // current delimiter token back on
+      this.pushToken(tok);
+      node = this.parseAggregate();
+    }
+
+    if (!noPostfix) {
+      node = this.parsePostfix(node);
+    }
+
+    if (node) {
+      return node;
+    } else {
+      throw this.error("unexpected token: " + tok.value, tok.lineno, tok.colno);
+    }
+  };
+
+  _proto.parseFilterName = function parseFilterName() {
+    var tok = this.expect(lexer.TOKEN_SYMBOL);
+    var name = tok.value;
+
+    while (this.skipValue(lexer.TOKEN_OPERATOR, '.')) {
+      name += '.' + this.expect(lexer.TOKEN_SYMBOL).value;
+    }
+
+    return new nodes.Symbol(tok.lineno, tok.colno, name);
+  };
+
+  _proto.parseFilterArgs = function parseFilterArgs(node) {
+    if (this.peekToken().type === lexer.TOKEN_LEFT_PAREN) {
+      // Get a FunCall node and add the parameters to the
+      // filter
+      var call = this.parsePostfix(node);
+      return call.args.children;
+    }
+
+    return [];
+  };
+
+  _proto.parseFilter = function parseFilter(node) {
+    while (this.skip(lexer.TOKEN_PIPE)) {
+      var name = this.parseFilterName();
+      node = new nodes.Filter(name.lineno, name.colno, name, new nodes.NodeList(name.lineno, name.colno, [node].concat(this.parseFilterArgs(node))));
+    }
+
+    return node;
+  };
+
+  _proto.parseFilterStatement = function parseFilterStatement() {
+    var filterTok = this.peekToken();
+
+    if (!this.skipSymbol('filter')) {
+      this.fail('parseFilterStatement: expected filter');
+    }
+
+    var name = this.parseFilterName();
+    var args = this.parseFilterArgs(name);
+    this.advanceAfterBlockEnd(filterTok.value);
+    var body = new nodes.Capture(name.lineno, name.colno, this.parseUntilBlocks('endfilter'));
+    this.advanceAfterBlockEnd();
+    var node = new nodes.Filter(name.lineno, name.colno, name, new nodes.NodeList(name.lineno, name.colno, [body].concat(args)));
+    return new nodes.Output(name.lineno, name.colno, [node]);
+  };
+
+  _proto.parseAggregate = function parseAggregate() {
+    var tok = this.nextToken();
+    var node;
+
+    switch (tok.type) {
+      case lexer.TOKEN_LEFT_PAREN:
+        node = new nodes.Group(tok.lineno, tok.colno);
+        break;
+
+      case lexer.TOKEN_LEFT_BRACKET:
+        node = new nodes.Array(tok.lineno, tok.colno);
+        break;
+
+      case lexer.TOKEN_LEFT_CURLY:
+        node = new nodes.Dict(tok.lineno, tok.colno);
+        break;
+
+      default:
+        return null;
+    }
+
+    while (1) {
+      // eslint-disable-line no-constant-condition
+      var type = this.peekToken().type;
+
+      if (type === lexer.TOKEN_RIGHT_PAREN || type === lexer.TOKEN_RIGHT_BRACKET || type === lexer.TOKEN_RIGHT_CURLY) {
+        this.nextToken();
+        break;
+      }
+
+      if (node.children.length > 0) {
+        if (!this.skip(lexer.TOKEN_COMMA)) {
+          this.fail('parseAggregate: expected comma after expression', tok.lineno, tok.colno);
+        }
+      }
+
+      if (node instanceof nodes.Dict) {
+        // TODO: check for errors
+        var key = this.parsePrimary(); // We expect a key/value pair for dicts, separated by a
+        // colon
+
+        if (!this.skip(lexer.TOKEN_COLON)) {
+          this.fail('parseAggregate: expected colon after dict key', tok.lineno, tok.colno);
+        } // TODO: check for errors
+
+
+        var value = this.parseExpression();
+        node.addChild(new nodes.Pair(key.lineno, key.colno, key, value));
+      } else {
+        // TODO: check for errors
+        var expr = this.parseExpression();
+        node.addChild(expr);
+      }
+    }
+
+    return node;
+  };
+
+  _proto.parseSignature = function parseSignature(tolerant, noParens) {
+    var tok = this.peekToken();
+
+    if (!noParens && tok.type !== lexer.TOKEN_LEFT_PAREN) {
+      if (tolerant) {
+        return null;
+      } else {
+        this.fail('expected arguments', tok.lineno, tok.colno);
+      }
+    }
+
+    if (tok.type === lexer.TOKEN_LEFT_PAREN) {
+      tok = this.nextToken();
+    }
+
+    var args = new nodes.NodeList(tok.lineno, tok.colno);
+    var kwargs = new nodes.KeywordArgs(tok.lineno, tok.colno);
+    var checkComma = false;
+
+    while (1) {
+      // eslint-disable-line no-constant-condition
+      tok = this.peekToken();
+
+      if (!noParens && tok.type === lexer.TOKEN_RIGHT_PAREN) {
+        this.nextToken();
+        break;
+      } else if (noParens && tok.type === lexer.TOKEN_BLOCK_END) {
+        break;
+      }
+
+      if (checkComma && !this.skip(lexer.TOKEN_COMMA)) {
+        this.fail('parseSignature: expected comma after expression', tok.lineno, tok.colno);
+      } else {
+        var arg = this.parseExpression();
+
+        if (this.skipValue(lexer.TOKEN_OPERATOR, '=')) {
+          kwargs.addChild(new nodes.Pair(arg.lineno, arg.colno, arg, this.parseExpression()));
+        } else {
+          args.addChild(arg);
+        }
+      }
+
+      checkComma = true;
+    }
+
+    if (kwargs.children.length) {
+      args.addChild(kwargs);
+    }
+
+    return args;
+  };
+
+  _proto.parseUntilBlocks = function parseUntilBlocks() {
+    var prev = this.breakOnBlocks;
+
+    for (var _len = arguments.length, blockNames = new Array(_len), _key = 0; _key < _len; _key++) {
+      blockNames[_key] = arguments[_key];
+    }
+
+    this.breakOnBlocks = blockNames;
+    var ret = this.parse();
+    this.breakOnBlocks = prev;
+    return ret;
+  };
+
+  _proto.parseNodes = function parseNodes() {
+    var tok;
+    var buf = [];
+
+    while (tok = this.nextToken()) {
+      if (tok.type === lexer.TOKEN_DATA) {
+        var data = tok.value;
+        var nextToken = this.peekToken();
+        var nextVal = nextToken && nextToken.value; // If the last token has "-" we need to trim the
+        // leading whitespace of the data. This is marked with
+        // the `dropLeadingWhitespace` variable.
+
+        if (this.dropLeadingWhitespace) {
+          // TODO: this could be optimized (don't use regex)
+          data = data.replace(/^\s*/, '');
+          this.dropLeadingWhitespace = false;
+        } // Same for the succeeding block start token
+
+
+        if (nextToken && (nextToken.type === lexer.TOKEN_BLOCK_START && nextVal.charAt(nextVal.length - 1) === '-' || nextToken.type === lexer.TOKEN_VARIABLE_START && nextVal.charAt(this.tokens.tags.VARIABLE_START.length) === '-' || nextToken.type === lexer.TOKEN_COMMENT && nextVal.charAt(this.tokens.tags.COMMENT_START.length) === '-')) {
+          // TODO: this could be optimized (don't use regex)
+          data = data.replace(/\s*$/, '');
+        }
+
+        buf.push(new nodes.Output(tok.lineno, tok.colno, [new nodes.TemplateData(tok.lineno, tok.colno, data)]));
+      } else if (tok.type === lexer.TOKEN_BLOCK_START) {
+        this.dropLeadingWhitespace = false;
+        var n = this.parseStatement();
+
+        if (!n) {
+          break;
+        }
+
+        buf.push(n);
+      } else if (tok.type === lexer.TOKEN_VARIABLE_START) {
+        var e = this.parseExpression();
+        this.dropLeadingWhitespace = false;
+        this.advanceAfterVariableEnd();
+        buf.push(new nodes.Output(tok.lineno, tok.colno, [e]));
+      } else if (tok.type === lexer.TOKEN_COMMENT) {
+        this.dropLeadingWhitespace = tok.value.charAt(tok.value.length - this.tokens.tags.COMMENT_END.length - 1) === '-';
+      } else {
+        // Ignore comments, otherwise this should be an error
+        this.fail('Unexpected token at top-level: ' + tok.type, tok.lineno, tok.colno);
+      }
+    }
+
+    return buf;
+  };
+
+  _proto.parse = function parse() {
+    return new nodes.NodeList(0, 0, this.parseNodes());
+  };
+
+  _proto.parseAsRoot = function parseAsRoot() {
+    return new nodes.Root(0, 0, this.parseNodes());
+  };
+
+  return Parser;
+}(Obj); // var util = require('util');
+// var l = lexer.lex('{%- if x -%}\n hello {% endif %}');
+// var t;
+// while((t = l.nextToken())) {
+//     console.log(util.inspect(t));
+// }
+// var p = new Parser(lexer.lex('hello {% filter title %}' +
+//                              'Hello madam how are you' +
+//                              '{% endfilter %}'));
+// var n = p.parseAsRoot();
+// nodes.printNodes(n);
+
+
+module.exports = {
+  parse: function parse(src, extensions, opts) {
+    var p = new Parser(lexer.lex(src, opts));
+
+    if (extensions !== undefined) {
+      p.extensions = extensions;
+    }
+
+    return p.parseAsRoot();
+  },
+  Parser: Parser
+};
+
+/***/ }),
+/* 9 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var lib = __webpack_require__(0);
+
+var whitespaceChars = " \n\t\r\xA0";
+var delimChars = '()[]{}%*-+~/#,:|.<>=!';
+var intChars = '0123456789';
+var BLOCK_START = '{%';
+var BLOCK_END = '%}';
+var VARIABLE_START = '{{';
+var VARIABLE_END = '}}';
+var COMMENT_START = '{#';
+var COMMENT_END = '#}';
+var TOKEN_STRING = 'string';
+var TOKEN_WHITESPACE = 'whitespace';
+var TOKEN_DATA = 'data';
+var TOKEN_BLOCK_START = 'block-start';
+var TOKEN_BLOCK_END = 'block-end';
+var TOKEN_VARIABLE_START = 'variable-start';
+var TOKEN_VARIABLE_END = 'variable-end';
+var TOKEN_COMMENT = 'comment';
+var TOKEN_LEFT_PAREN = 'left-paren';
+var TOKEN_RIGHT_PAREN = 'right-paren';
+var TOKEN_LEFT_BRACKET = 'left-bracket';
+var TOKEN_RIGHT_BRACKET = 'right-bracket';
+var TOKEN_LEFT_CURLY = 'left-curly';
+var TOKEN_RIGHT_CURLY = 'right-curly';
+var TOKEN_OPERATOR = 'operator';
+var TOKEN_COMMA = 'comma';
+var TOKEN_COLON = 'colon';
+var TOKEN_TILDE = 'tilde';
+var TOKEN_PIPE = 'pipe';
+var TOKEN_INT = 'int';
+var TOKEN_FLOAT = 'float';
+var TOKEN_BOOLEAN = 'boolean';
+var TOKEN_NONE = 'none';
+var TOKEN_SYMBOL = 'symbol';
+var TOKEN_SPECIAL = 'special';
+var TOKEN_REGEX = 'regex';
+
+function token(type, value, lineno, colno) {
+  return {
+    type: type,
+    value: value,
+    lineno: lineno,
+    colno: colno
+  };
+}
+
+var Tokenizer =
+/*#__PURE__*/
+function () {
+  function Tokenizer(str, opts) {
+    this.str = str;
+    this.index = 0;
+    this.len = str.length;
+    this.lineno = 0;
+    this.colno = 0;
+    this.in_code = false;
+    opts = opts || {};
+    var tags = opts.tags || {};
+    this.tags = {
+      BLOCK_START: tags.blockStart || BLOCK_START,
+      BLOCK_END: tags.blockEnd || BLOCK_END,
+      VARIABLE_START: tags.variableStart || VARIABLE_START,
+      VARIABLE_END: tags.variableEnd || VARIABLE_END,
+      COMMENT_START: tags.commentStart || COMMENT_START,
+      COMMENT_END: tags.commentEnd || COMMENT_END
+    };
+    this.trimBlocks = !!opts.trimBlocks;
+    this.lstripBlocks = !!opts.lstripBlocks;
+  }
+
+  var _proto = Tokenizer.prototype;
+
+  _proto.nextToken = function nextToken() {
+    var lineno = this.lineno;
+    var colno = this.colno;
+    var tok;
+
+    if (this.in_code) {
+      // Otherwise, if we are in a block parse it as code
+      var cur = this.current();
+
+      if (this.isFinished()) {
+        // We have nothing else to parse
+        return null;
+      } else if (cur === '"' || cur === '\'') {
+        // We've hit a string
+        return token(TOKEN_STRING, this._parseString(cur), lineno, colno);
+      } else if (tok = this._extract(whitespaceChars)) {
+        // We hit some whitespace
+        return token(TOKEN_WHITESPACE, tok, lineno, colno);
+      } else if ((tok = this._extractString(this.tags.BLOCK_END)) || (tok = this._extractString('-' + this.tags.BLOCK_END))) {
+        // Special check for the block end tag
+        //
+        // It is a requirement that start and end tags are composed of
+        // delimiter characters (%{}[] etc), and our code always
+        // breaks on delimiters so we can assume the token parsing
+        // doesn't consume these elsewhere
+        this.in_code = false;
+
+        if (this.trimBlocks) {
+          cur = this.current();
+
+          if (cur === '\n') {
+            // Skip newline
+            this.forward();
+          } else if (cur === '\r') {
+            // Skip CRLF newline
+            this.forward();
+            cur = this.current();
+
+            if (cur === '\n') {
+              this.forward();
+            } else {
+              // Was not a CRLF, so go back
+              this.back();
+            }
+          }
+        }
+
+        return token(TOKEN_BLOCK_END, tok, lineno, colno);
+      } else if ((tok = this._extractString(this.tags.VARIABLE_END)) || (tok = this._extractString('-' + this.tags.VARIABLE_END))) {
+        // Special check for variable end tag (see above)
+        this.in_code = false;
+        return token(TOKEN_VARIABLE_END, tok, lineno, colno);
+      } else if (cur === 'r' && this.str.charAt(this.index + 1) === '/') {
+        // Skip past 'r/'.
+        this.forwardN(2); // Extract until the end of the regex -- / ends it, \/ does not.
+
+        var regexBody = '';
+
+        while (!this.isFinished()) {
+          if (this.current() === '/' && this.previous() !== '\\') {
+            this.forward();
+            break;
+          } else {
+            regexBody += this.current();
+            this.forward();
+          }
+        } // Check for flags.
+        // The possible flags are according to https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
+
+
+        var POSSIBLE_FLAGS = ['g', 'i', 'm', 'y'];
+        var regexFlags = '';
+
+        while (!this.isFinished()) {
+          var isCurrentAFlag = POSSIBLE_FLAGS.indexOf(this.current()) !== -1;
+
+          if (isCurrentAFlag) {
+            regexFlags += this.current();
+            this.forward();
+          } else {
+            break;
+          }
+        }
+
+        return token(TOKEN_REGEX, {
+          body: regexBody,
+          flags: regexFlags
+        }, lineno, colno);
+      } else if (delimChars.indexOf(cur) !== -1) {
+        // We've hit a delimiter (a special char like a bracket)
+        this.forward();
+        var complexOps = ['==', '===', '!=', '!==', '<=', '>=', '//', '**'];
+        var curComplex = cur + this.current();
+        var type;
+
+        if (lib.indexOf(complexOps, curComplex) !== -1) {
+          this.forward();
+          cur = curComplex; // See if this is a strict equality/inequality comparator
+
+          if (lib.indexOf(complexOps, curComplex + this.current()) !== -1) {
+            cur = curComplex + this.current();
+            this.forward();
+          }
+        }
+
+        switch (cur) {
+          case '(':
+            type = TOKEN_LEFT_PAREN;
+            break;
+
+          case ')':
+            type = TOKEN_RIGHT_PAREN;
+            break;
+
+          case '[':
+            type = TOKEN_LEFT_BRACKET;
+            break;
+
+          case ']':
+            type = TOKEN_RIGHT_BRACKET;
+            break;
+
+          case '{':
+            type = TOKEN_LEFT_CURLY;
+            break;
+
+          case '}':
+            type = TOKEN_RIGHT_CURLY;
+            break;
+
+          case ',':
+            type = TOKEN_COMMA;
+            break;
+
+          case ':':
+            type = TOKEN_COLON;
+            break;
+
+          case '~':
+            type = TOKEN_TILDE;
+            break;
+
+          case '|':
+            type = TOKEN_PIPE;
+            break;
+
+          default:
+            type = TOKEN_OPERATOR;
+        }
+
+        return token(type, cur, lineno, colno);
+      } else {
+        // We are not at whitespace or a delimiter, so extract the
+        // text and parse it
+        tok = this._extractUntil(whitespaceChars + delimChars);
+
+        if (tok.match(/^[-+]?[0-9]+$/)) {
+          if (this.current() === '.') {
+            this.forward();
+
+            var dec = this._extract(intChars);
+
+            return token(TOKEN_FLOAT, tok + '.' + dec, lineno, colno);
+          } else {
+            return token(TOKEN_INT, tok, lineno, colno);
+          }
+        } else if (tok.match(/^(true|false)$/)) {
+          return token(TOKEN_BOOLEAN, tok, lineno, colno);
+        } else if (tok === 'none') {
+          return token(TOKEN_NONE, tok, lineno, colno);
+          /*
+           * Added to make the test `null is null` evaluate truthily.
+           * Otherwise, Nunjucks will look up null in the context and
+           * return `undefined`, which is not what we want. This *may* have
+           * consequences is someone is using null in their templates as a
+           * variable.
+           */
+        } else if (tok === 'null') {
+          return token(TOKEN_NONE, tok, lineno, colno);
+        } else if (tok) {
+          return token(TOKEN_SYMBOL, tok, lineno, colno);
+        } else {
+          throw new Error('Unexpected value while parsing: ' + tok);
+        }
+      }
+    } else {
+      // Parse out the template text, breaking on tag
+      // delimiters because we need to look for block/variable start
+      // tags (don't use the full delimChars for optimization)
+      var beginChars = this.tags.BLOCK_START.charAt(0) + this.tags.VARIABLE_START.charAt(0) + this.tags.COMMENT_START.charAt(0) + this.tags.COMMENT_END.charAt(0);
+
+      if (this.isFinished()) {
+        return null;
+      } else if ((tok = this._extractString(this.tags.BLOCK_START + '-')) || (tok = this._extractString(this.tags.BLOCK_START))) {
+        this.in_code = true;
+        return token(TOKEN_BLOCK_START, tok, lineno, colno);
+      } else if ((tok = this._extractString(this.tags.VARIABLE_START + '-')) || (tok = this._extractString(this.tags.VARIABLE_START))) {
+        this.in_code = true;
+        return token(TOKEN_VARIABLE_START, tok, lineno, colno);
+      } else {
+        tok = '';
+        var data;
+        var inComment = false;
+
+        if (this._matches(this.tags.COMMENT_START)) {
+          inComment = true;
+          tok = this._extractString(this.tags.COMMENT_START);
+        } // Continually consume text, breaking on the tag delimiter
+        // characters and checking to see if it's a start tag.
+        //
+        // We could hit the end of the template in the middle of
+        // our looping, so check for the null return value from
+        // _extractUntil
+
+
+        while ((data = this._extractUntil(beginChars)) !== null) {
+          tok += data;
+
+          if ((this._matches(this.tags.BLOCK_START) || this._matches(this.tags.VARIABLE_START) || this._matches(this.tags.COMMENT_START)) && !inComment) {
+            if (this.lstripBlocks && this._matches(this.tags.BLOCK_START) && this.colno > 0 && this.colno <= tok.length) {
+              var lastLine = tok.slice(-this.colno);
+
+              if (/^\s+$/.test(lastLine)) {
+                // Remove block leading whitespace from beginning of the string
+                tok = tok.slice(0, -this.colno);
+
+                if (!tok.length) {
+                  // All data removed, collapse to avoid unnecessary nodes
+                  // by returning next token (block start)
+                  return this.nextToken();
+                }
+              }
+            } // If it is a start tag, stop looping
+
+
+            break;
+          } else if (this._matches(this.tags.COMMENT_END)) {
+            if (!inComment) {
+              throw new Error('unexpected end of comment');
+            }
+
+            tok += this._extractString(this.tags.COMMENT_END);
+            break;
+          } else {
+            // It does not match any tag, so add the character and
+            // carry on
+            tok += this.current();
+            this.forward();
+          }
+        }
+
+        if (data === null && inComment) {
+          throw new Error('expected end of comment, got end of file');
+        }
+
+        return token(inComment ? TOKEN_COMMENT : TOKEN_DATA, tok, lineno, colno);
+      }
+    }
+  };
+
+  _proto._parseString = function _parseString(delimiter) {
+    this.forward();
+    var str = '';
+
+    while (!this.isFinished() && this.current() !== delimiter) {
+      var cur = this.current();
+
+      if (cur === '\\') {
+        this.forward();
+
+        switch (this.current()) {
+          case 'n':
+            str += '\n';
+            break;
+
+          case 't':
+            str += '\t';
+            break;
+
+          case 'r':
+            str += '\r';
+            break;
+
+          default:
+            str += this.current();
+        }
+
+        this.forward();
+      } else {
+        str += cur;
+        this.forward();
+      }
+    }
+
+    this.forward();
+    return str;
+  };
+
+  _proto._matches = function _matches(str) {
+    if (this.index + str.length > this.len) {
+      return null;
+    }
+
+    var m = this.str.slice(this.index, this.index + str.length);
+    return m === str;
+  };
+
+  _proto._extractString = function _extractString(str) {
+    if (this._matches(str)) {
+      this.forwardN(str.length);
+      return str;
+    }
+
+    return null;
+  };
+
+  _proto._extractUntil = function _extractUntil(charString) {
+    // Extract all non-matching chars, with the default matching set
+    // to everything
+    return this._extractMatching(true, charString || '');
+  };
+
+  _proto._extract = function _extract(charString) {
+    // Extract all matching chars (no default, so charString must be
+    // explicit)
+    return this._extractMatching(false, charString);
+  };
+
+  _proto._extractMatching = function _extractMatching(breakOnMatch, charString) {
+    // Pull out characters until a breaking char is hit.
+    // If breakOnMatch is false, a non-matching char stops it.
+    // If breakOnMatch is true, a matching char stops it.
+    if (this.isFinished()) {
+      return null;
+    }
+
+    var first = charString.indexOf(this.current()); // Only proceed if the first character doesn't meet our condition
+
+    if (breakOnMatch && first === -1 || !breakOnMatch && first !== -1) {
+      var t = this.current();
+      this.forward(); // And pull out all the chars one at a time until we hit a
+      // breaking char
+
+      var idx = charString.indexOf(this.current());
+
+      while ((breakOnMatch && idx === -1 || !breakOnMatch && idx !== -1) && !this.isFinished()) {
+        t += this.current();
+        this.forward();
+        idx = charString.indexOf(this.current());
+      }
+
+      return t;
+    }
+
+    return '';
+  };
+
+  _proto._extractRegex = function _extractRegex(regex) {
+    var matches = this.currentStr().match(regex);
+
+    if (!matches) {
+      return null;
+    } // Move forward whatever was matched
+
+
+    this.forwardN(matches[0].length);
+    return matches;
+  };
+
+  _proto.isFinished = function isFinished() {
+    return this.index >= this.len;
+  };
+
+  _proto.forwardN = function forwardN(n) {
+    for (var i = 0; i < n; i++) {
+      this.forward();
+    }
+  };
+
+  _proto.forward = function forward() {
+    this.index++;
+
+    if (this.previous() === '\n') {
+      this.lineno++;
+      this.colno = 0;
+    } else {
+      this.colno++;
+    }
+  };
+
+  _proto.backN = function backN(n) {
+    for (var i = 0; i < n; i++) {
+      this.back();
+    }
+  };
+
+  _proto.back = function back() {
+    this.index--;
+
+    if (this.current() === '\n') {
+      this.lineno--;
+      var idx = this.src.lastIndexOf('\n', this.index - 1);
+
+      if (idx === -1) {
+        this.colno = this.index;
+      } else {
+        this.colno = this.index - idx;
+      }
+    } else {
+      this.colno--;
+    }
+  }; // current returns current character
+
+
+  _proto.current = function current() {
+    if (!this.isFinished()) {
+      return this.str.charAt(this.index);
+    }
+
+    return '';
+  }; // currentStr returns what's left of the unparsed string
+
+
+  _proto.currentStr = function currentStr() {
+    if (!this.isFinished()) {
+      return this.str.substr(this.index);
+    }
+
+    return '';
+  };
+
+  _proto.previous = function previous() {
+    return this.str.charAt(this.index - 1);
+  };
+
+  return Tokenizer;
+}();
+
+module.exports = {
+  lex: function lex(src, opts) {
+    return new Tokenizer(src, opts);
+  },
+  TOKEN_STRING: TOKEN_STRING,
+  TOKEN_WHITESPACE: TOKEN_WHITESPACE,
+  TOKEN_DATA: TOKEN_DATA,
+  TOKEN_BLOCK_START: TOKEN_BLOCK_START,
+  TOKEN_BLOCK_END: TOKEN_BLOCK_END,
+  TOKEN_VARIABLE_START: TOKEN_VARIABLE_START,
+  TOKEN_VARIABLE_END: TOKEN_VARIABLE_END,
+  TOKEN_COMMENT: TOKEN_COMMENT,
+  TOKEN_LEFT_PAREN: TOKEN_LEFT_PAREN,
+  TOKEN_RIGHT_PAREN: TOKEN_RIGHT_PAREN,
+  TOKEN_LEFT_BRACKET: TOKEN_LEFT_BRACKET,
+  TOKEN_RIGHT_BRACKET: TOKEN_RIGHT_BRACKET,
+  TOKEN_LEFT_CURLY: TOKEN_LEFT_CURLY,
+  TOKEN_RIGHT_CURLY: TOKEN_RIGHT_CURLY,
+  TOKEN_OPERATOR: TOKEN_OPERATOR,
+  TOKEN_COMMA: TOKEN_COMMA,
+  TOKEN_COLON: TOKEN_COLON,
+  TOKEN_TILDE: TOKEN_TILDE,
+  TOKEN_PIPE: TOKEN_PIPE,
+  TOKEN_INT: TOKEN_INT,
+  TOKEN_FLOAT: TOKEN_FLOAT,
+  TOKEN_BOOLEAN: TOKEN_BOOLEAN,
+  TOKEN_NONE: TOKEN_NONE,
+  TOKEN_SYMBOL: TOKEN_SYMBOL,
+  TOKEN_SPECIAL: TOKEN_SPECIAL,
+  TOKEN_REGEX: TOKEN_REGEX
+};
+
+/***/ }),
+/* 10 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var Loader = __webpack_require__(6);
+
+var _require = __webpack_require__(19),
+    PrecompiledLoader = _require.PrecompiledLoader;
+
+var WebLoader =
+/*#__PURE__*/
+function (_Loader) {
+  _inheritsLoose(WebLoader, _Loader);
+
+  function WebLoader(baseURL, opts) {
+    var _this;
+
+    _this = _Loader.call(this) || this;
+    _this.baseURL = baseURL || '.';
+    opts = opts || {}; // By default, the cache is turned off because there's no way
+    // to "watch" templates over HTTP, so they are re-downloaded
+    // and compiled each time. (Remember, PRECOMPILE YOUR
+    // TEMPLATES in production!)
+
+    _this.useCache = !!opts.useCache; // We default `async` to false so that the simple synchronous
+    // API can be used when you aren't doing anything async in
+    // your templates (which is most of the time). This performs a
+    // sync ajax request, but that's ok because it should *only*
+    // happen in development. PRECOMPILE YOUR TEMPLATES.
+
+    _this.async = !!opts.async;
+    return _this;
+  }
+
+  var _proto = WebLoader.prototype;
+
+  _proto.resolve = function resolve(from, to) {
+    throw new Error('relative templates not support in the browser yet');
+  };
+
+  _proto.getSource = function getSource(name, cb) {
+    var _this2 = this;
+
+    var useCache = this.useCache;
+    var result;
+    this.fetch(this.baseURL + '/' + name, function (err, src) {
+      if (err) {
+        if (cb) {
+          cb(err.content);
+        } else if (err.status === 404) {
+          result = null;
+        } else {
+          throw err.content;
+        }
+      } else {
+        result = {
+          src: src,
+          path: name,
+          noCache: !useCache
+        };
+
+        _this2.emit('load', name, result);
+
+        if (cb) {
+          cb(null, result);
+        }
+      }
+    }); // if this WebLoader isn't running asynchronously, the
+    // fetch above would actually run sync and we'll have a
+    // result here
+
+    return result;
+  };
+
+  _proto.fetch = function fetch(url, cb) {
+    // Only in the browser please
+    if (typeof window === 'undefined') {
+      throw new Error('WebLoader can only by used in a browser');
+    }
+
+    var ajax = new XMLHttpRequest();
+    var loading = true;
+
+    ajax.onreadystatechange = function () {
+      if (ajax.readyState === 4 && loading) {
+        loading = false;
+
+        if (ajax.status === 0 || ajax.status === 200) {
+          cb(null, ajax.responseText);
+        } else {
+          cb({
+            status: ajax.status,
+            content: ajax.responseText
+          });
+        }
+      }
+    };
+
+    url += (url.indexOf('?') === -1 ? '?' : '&') + 's=' + new Date().getTime();
+    ajax.open('GET', url, this.async);
+    ajax.send();
+  };
+
+  return WebLoader;
+}(Loader);
+
+module.exports = {
+  WebLoader: WebLoader,
+  PrecompiledLoader: PrecompiledLoader
+};
+
+/***/ }),
+/* 11 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var lib = __webpack_require__(0);
+
+var _require = __webpack_require__(7),
+    Environment = _require.Environment,
+    Template = _require.Template;
+
+var Loader = __webpack_require__(6);
+
+var loaders = __webpack_require__(10);
+
+var precompile = __webpack_require__(23);
+
+var compiler = __webpack_require__(5);
+
+var parser = __webpack_require__(8);
+
+var lexer = __webpack_require__(9);
+
+var runtime = __webpack_require__(2);
+
+var nodes = __webpack_require__(3);
+
+var installJinjaCompat = __webpack_require__(25); // A single instance of an environment, since this is so commonly used
+
+
+var e;
+
+function configure(templatesPath, opts) {
+  opts = opts || {};
+
+  if (lib.isObject(templatesPath)) {
+    opts = templatesPath;
+    templatesPath = null;
+  }
+
+  var TemplateLoader;
+
+  if (loaders.FileSystemLoader) {
+    TemplateLoader = new loaders.FileSystemLoader(templatesPath, {
+      watch: opts.watch,
+      noCache: opts.noCache
+    });
+  } else if (loaders.WebLoader) {
+    TemplateLoader = new loaders.WebLoader(templatesPath, {
+      useCache: opts.web && opts.web.useCache,
+      async: opts.web && opts.web.async
+    });
+  }
+
+  e = new Environment(TemplateLoader, opts);
+
+  if (opts && opts.express) {
+    e.express(opts.express);
+  }
+
+  return e;
+}
+
+module.exports = {
+  Environment: Environment,
+  Template: Template,
+  Loader: Loader,
+  FileSystemLoader: loaders.FileSystemLoader,
+  NodeResolveLoader: loaders.NodeResolveLoader,
+  PrecompiledLoader: loaders.PrecompiledLoader,
+  WebLoader: loaders.WebLoader,
+  compiler: compiler,
+  parser: parser,
+  lexer: lexer,
+  runtime: runtime,
+  lib: lib,
+  nodes: nodes,
+  installJinjaCompat: installJinjaCompat,
+  configure: configure,
+  reset: function reset() {
+    e = undefined;
+  },
+  compile: function compile(src, env, path, eagerCompile) {
+    if (!e) {
+      configure();
+    }
+
+    return new Template(src, env, path, eagerCompile);
+  },
+  render: function render(name, ctx, cb) {
+    if (!e) {
+      configure();
+    }
+
+    return e.render(name, ctx, cb);
+  },
+  renderString: function renderString(src, ctx, cb) {
+    if (!e) {
+      configure();
+    }
+
+    return e.renderString(src, ctx, cb);
+  },
+  precompile: precompile ? precompile.precompile : undefined,
+  precompileString: precompile ? precompile.precompileString : undefined
+};
+
+/***/ }),
+/* 12 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+// rawAsap provides everything we need except exception management.
+var rawAsap = __webpack_require__(13);
+// RawTasks are recycled to reduce GC churn.
+var freeTasks = [];
+// We queue errors to ensure they are thrown in right order (FIFO).
+// Array-as-queue is good enough here, since we are just dealing with exceptions.
+var pendingErrors = [];
+var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError);
+
+function throwFirstError() {
+    if (pendingErrors.length) {
+        throw pendingErrors.shift();
+    }
+}
+
+/**
+ * Calls a task as soon as possible after returning, in its own event, with priority
+ * over other events like animation, reflow, and repaint. An error thrown from an
+ * event will not interrupt, nor even substantially slow down the processing of
+ * other events, but will be rather postponed to a lower priority event.
+ * @param {{call}} task A callable object, typically a function that takes no
+ * arguments.
+ */
+module.exports = asap;
+function asap(task) {
+    var rawTask;
+    if (freeTasks.length) {
+        rawTask = freeTasks.pop();
+    } else {
+        rawTask = new RawTask();
+    }
+    rawTask.task = task;
+    rawAsap(rawTask);
+}
+
+// We wrap tasks with recyclable task objects.  A task object implements
+// `call`, just like a function.
+function RawTask() {
+    this.task = null;
+}
+
+// The sole purpose of wrapping the task is to catch the exception and recycle
+// the task object after its single use.
+RawTask.prototype.call = function () {
+    try {
+        this.task.call();
+    } catch (error) {
+        if (asap.onerror) {
+            // This hook exists purely for testing purposes.
+            // Its name will be periodically randomized to break any code that
+            // depends on its existence.
+            asap.onerror(error);
+        } else {
+            // In a web browser, exceptions are not fatal. However, to avoid
+            // slowing down the queue of pending tasks, we rethrow the error in a
+            // lower priority turn.
+            pendingErrors.push(error);
+            requestErrorThrow();
+        }
+    } finally {
+        this.task = null;
+        freeTasks[freeTasks.length] = this;
+    }
+};
+
+
+/***/ }),
+/* 13 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {
+
+// Use the fastest means possible to execute a task in its own turn, with
+// priority over other events including IO, animation, reflow, and redraw
+// events in browsers.
+//
+// An exception thrown by a task will permanently interrupt the processing of
+// subsequent tasks. The higher level `asap` function ensures that if an
+// exception is thrown by a task, that the task queue will continue flushing as
+// soon as possible, but if you use `rawAsap` directly, you are responsible to
+// either ensure that no exceptions are thrown from your task, or to manually
+// call `rawAsap.requestFlush` if an exception is thrown.
+module.exports = rawAsap;
+function rawAsap(task) {
+    if (!queue.length) {
+        requestFlush();
+        flushing = true;
+    }
+    // Equivalent to push, but avoids a function call.
+    queue[queue.length] = task;
+}
+
+var queue = [];
+// Once a flush has been requested, no further calls to `requestFlush` are
+// necessary until the next `flush` completes.
+var flushing = false;
+// `requestFlush` is an implementation-specific method that attempts to kick
+// off a `flush` event as quickly as possible. `flush` will attempt to exhaust
+// the event queue before yielding to the browser's own event loop.
+var requestFlush;
+// The position of the next task to execute in the task queue. This is
+// preserved between calls to `flush` so that it can be resumed if
+// a task throws an exception.
+var index = 0;
+// If a task schedules additional tasks recursively, the task queue can grow
+// unbounded. To prevent memory exhaustion, the task queue will periodically
+// truncate already-completed tasks.
+var capacity = 1024;
+
+// The flush function processes all tasks that have been scheduled with
+// `rawAsap` unless and until one of those tasks throws an exception.
+// If a task throws an exception, `flush` ensures that its state will remain
+// consistent and will resume where it left off when called again.
+// However, `flush` does not make any arrangements to be called again if an
+// exception is thrown.
+function flush() {
+    while (index < queue.length) {
+        var currentIndex = index;
+        // Advance the index before calling the task. This ensures that we will
+        // begin flushing on the next task the task throws an error.
+        index = index + 1;
+        queue[currentIndex].call();
+        // Prevent leaking memory for long chains of recursive calls to `asap`.
+        // If we call `asap` within tasks scheduled by `asap`, the queue will
+        // grow, but to avoid an O(n) walk for every task we execute, we don't
+        // shift tasks off the queue after they have been executed.
+        // Instead, we periodically shift 1024 tasks off the queue.
+        if (index > capacity) {
+            // Manually shift all values starting at the index back to the
+            // beginning of the queue.
+            for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
+                queue[scan] = queue[scan + index];
+            }
+            queue.length -= index;
+            index = 0;
+        }
+    }
+    queue.length = 0;
+    index = 0;
+    flushing = false;
+}
+
+// `requestFlush` is implemented using a strategy based on data collected from
+// every available SauceLabs Selenium web driver worker at time of writing.
+// https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593
+
+// Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that
+// have WebKitMutationObserver but not un-prefixed MutationObserver.
+// Must use `global` or `self` instead of `window` to work in both frames and web
+// workers. `global` is a provision of Browserify, Mr, Mrs, or Mop.
+
+/* globals self */
+var scope = typeof global !== "undefined" ? global : self;
+var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;
+
+// MutationObservers are desirable because they have high priority and work
+// reliably everywhere they are implemented.
+// They are implemented in all modern browsers.
+//
+// - Android 4-4.3
+// - Chrome 26-34
+// - Firefox 14-29
+// - Internet Explorer 11
+// - iPad Safari 6-7.1
+// - iPhone Safari 7-7.1
+// - Safari 6-7
+if (typeof BrowserMutationObserver === "function") {
+    requestFlush = makeRequestCallFromMutationObserver(flush);
+
+// MessageChannels are desirable because they give direct access to the HTML
+// task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera
+// 11-12, and in web workers in many engines.
+// Although message channels yield to any queued rendering and IO tasks, they
+// would be better than imposing the 4ms delay of timers.
+// However, they do not work reliably in Internet Explorer or Safari.
+
+// Internet Explorer 10 is the only browser that has setImmediate but does
+// not have MutationObservers.
+// Although setImmediate yields to the browser's renderer, it would be
+// preferrable to falling back to setTimeout since it does not have
+// the minimum 4ms penalty.
+// Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and
+// Desktop to a lesser extent) that renders both setImmediate and
+// MessageChannel useless for the purposes of ASAP.
+// https://github.com/kriskowal/q/issues/396
+
+// Timers are implemented universally.
+// We fall back to timers in workers in most engines, and in foreground
+// contexts in the following browsers.
+// However, note that even this simple case requires nuances to operate in a
+// broad spectrum of browsers.
+//
+// - Firefox 3-13
+// - Internet Explorer 6-9
+// - iPad Safari 4.3
+// - Lynx 2.8.7
+} else {
+    requestFlush = makeRequestCallFromTimer(flush);
+}
+
+// `requestFlush` requests that the high priority event queue be flushed as
+// soon as possible.
+// This is useful to prevent an error thrown in a task from stalling the event
+// queue if the exception handled by Node.js’s
+// `process.on("uncaughtException")` or by a domain.
+rawAsap.requestFlush = requestFlush;
+
+// To request a high priority event, we induce a mutation observer by toggling
+// the text of a text node between "1" and "-1".
+function makeRequestCallFromMutationObserver(callback) {
+    var toggle = 1;
+    var observer = new BrowserMutationObserver(callback);
+    var node = document.createTextNode("");
+    observer.observe(node, {characterData: true});
+    return function requestCall() {
+        toggle = -toggle;
+        node.data = toggle;
+    };
+}
+
+// The message channel technique was discovered by Malte Ubl and was the
+// original foundation for this library.
+// http://www.nonblocking.io/2011/06/windownexttick.html
+
+// Safari 6.0.5 (at least) intermittently fails to create message ports on a
+// page's first load. Thankfully, this version of Safari supports
+// MutationObservers, so we don't need to fall back in that case.
+
+// function makeRequestCallFromMessageChannel(callback) {
+//     var channel = new MessageChannel();
+//     channel.port1.onmessage = callback;
+//     return function requestCall() {
+//         channel.port2.postMessage(0);
+//     };
+// }
+
+// For reasons explained above, we are also unable to use `setImmediate`
+// under any circumstances.
+// Even if we were, there is another bug in Internet Explorer 10.
+// It is not sufficient to assign `setImmediate` to `requestFlush` because
+// `setImmediate` must be called *by name* and therefore must be wrapped in a
+// closure.
+// Never forget.
+
+// function makeRequestCallFromSetImmediate(callback) {
+//     return function requestCall() {
+//         setImmediate(callback);
+//     };
+// }
+
+// Safari 6.0 has a problem where timers will get lost while the user is
+// scrolling. This problem does not impact ASAP because Safari 6.0 supports
+// mutation observers, so that implementation is used instead.
+// However, if we ever elect to use timers in Safari, the prevalent work-around
+// is to add a scroll event listener that calls for a flush.
+
+// `setTimeout` does not call the passed callback if the delay is less than
+// approximately 7 in web workers in Firefox 8 through 18, and sometimes not
+// even then.
+
+function makeRequestCallFromTimer(callback) {
+    return function requestCall() {
+        // We dispatch a timeout with a specified delay of 0 for engines that
+        // can reliably accommodate that request. This will usually be snapped
+        // to a 4 milisecond delay, but once we're flushing, there's no delay
+        // between events.
+        var timeoutHandle = setTimeout(handleTimer, 0);
+        // However, since this timer gets frequently dropped in Firefox
+        // workers, we enlist an interval handle that will try to fire
+        // an event 20 times per second until it succeeds.
+        var intervalHandle = setInterval(handleTimer, 50);
+
+        function handleTimer() {
+            // Whichever timer succeeds will cancel both timers and
+            // execute the callback.
+            clearTimeout(timeoutHandle);
+            clearInterval(intervalHandle);
+            callback();
+        }
+    };
+}
+
+// This is for `asap.js` only.
+// Its name will be periodically randomized to break any code that depends on
+// its existence.
+rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer;
+
+// ASAP was originally a nextTick shim included in Q. This was factored out
+// into this ASAP package. It was later adapted to RSVP which made further
+// amendments. These decisions, particularly to marginalize MessageChannel and
+// to capture the MutationObserver implementation in a closure, were integrated
+// back into ASAP proper.
+// https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js
+
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(14)))
+
+/***/ }),
+/* 14 */
+/***/ (function(module, exports) {
+
+var g;
+
+// This works in non-strict mode
+g = (function() {
+	return this;
+})();
+
+try {
+	// This works if eval is allowed (see CSP)
+	g = g || Function("return this")() || (1,eval)("this");
+} catch(e) {
+	// This works if the window reference is available
+	if(typeof window === "object")
+		g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
+
+
+/***/ }),
+/* 15 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// MIT license (by Elan Shanker).
+(function(globals) {
+  'use strict';
+
+  var executeSync = function(){
+    var args = Array.prototype.slice.call(arguments);
+    if (typeof args[0] === 'function'){
+      args[0].apply(null, args.splice(1));
+    }
+  };
+
+  var executeAsync = function(fn){
+    if (typeof setImmediate === 'function') {
+      setImmediate(fn);
+    } else if (typeof process !== 'undefined' && process.nextTick) {
+      process.nextTick(fn);
+    } else {
+      setTimeout(fn, 0);
+    }
+  };
+
+  var makeIterator = function (tasks) {
+    var makeCallback = function (index) {
+      var fn = function () {
+        if (tasks.length) {
+          tasks[index].apply(null, arguments);
+        }
+        return fn.next();
+      };
+      fn.next = function () {
+        return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+      };
+      return fn;
+    };
+    return makeCallback(0);
+  };
+  
+  var _isArray = Array.isArray || function(maybeArray){
+    return Object.prototype.toString.call(maybeArray) === '[object Array]';
+  };
+
+  var waterfall = function (tasks, callback, forceAsync) {
+    var nextTick = forceAsync ? executeAsync : executeSync;
+    callback = callback || function () {};
+    if (!_isArray(tasks)) {
+      var err = new Error('First argument to waterfall must be an array of functions');
+      return callback(err);
+    }
+    if (!tasks.length) {
+      return callback();
+    }
+    var wrapIterator = function (iterator) {
+      return function (err) {
+        if (err) {
+          callback.apply(null, arguments);
+          callback = function () {};
+        } else {
+          var args = Array.prototype.slice.call(arguments, 1);
+          var next = iterator.next();
+          if (next) {
+            args.push(wrapIterator(next));
+          } else {
+            args.push(callback);
+          }
+          nextTick(function () {
+            iterator.apply(null, args);
+          });
+        }
+      };
+    };
+    wrapIterator(makeIterator(tasks))();
+  };
+
+  if (true) {
+    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = (function () {
+      return waterfall;
+    }).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
+				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); // RequireJS
+  } else if (typeof module !== 'undefined' && module.exports) {
+    module.exports = waterfall; // CommonJS
+  } else {
+    globals.waterfall = waterfall; // <script>
+  }
+})(this);
+
+
+/***/ }),
+/* 16 */
+/***/ (function(module, exports) {
+
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+function EventEmitter() {
+  this._events = this._events || {};
+  this._maxListeners = this._maxListeners || undefined;
+}
+module.exports = EventEmitter;
+
+// Backwards-compat with node 0.10.x
+EventEmitter.EventEmitter = EventEmitter;
+
+EventEmitter.prototype._events = undefined;
+EventEmitter.prototype._maxListeners = undefined;
+
+// By default EventEmitters will print a warning if more than 10 listeners are
+// added to it. This is a useful default which helps finding memory leaks.
+EventEmitter.defaultMaxListeners = 10;
+
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+EventEmitter.prototype.setMaxListeners = function(n) {
+  if (!isNumber(n) || n < 0 || isNaN(n))
+    throw TypeError('n must be a positive number');
+  this._maxListeners = n;
+  return this;
+};
+
+EventEmitter.prototype.emit = function(type) {
+  var er, handler, len, args, i, listeners;
+
+  if (!this._events)
+    this._events = {};
+
+  // If there is no 'error' event listener then throw.
+  if (type === 'error') {
+    if (!this._events.error ||
+        (isObject(this._events.error) && !this._events.error.length)) {
+      er = arguments[1];
+      if (er instanceof Error) {
+        throw er; // Unhandled 'error' event
+      } else {
+        // At least give some kind of context to the user
+        var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
+        err.context = er;
+        throw err;
+      }
+    }
+  }
+
+  handler = this._events[type];
+
+  if (isUndefined(handler))
+    return false;
+
+  if (isFunction(handler)) {
+    switch (arguments.length) {
+      // fast cases
+      case 1:
+        handler.call(this);
+        break;
+      case 2:
+        handler.call(this, arguments[1]);
+        break;
+      case 3:
+        handler.call(this, arguments[1], arguments[2]);
+        break;
+      // slower
+      default:
+        args = Array.prototype.slice.call(arguments, 1);
+        handler.apply(this, args);
+    }
+  } else if (isObject(handler)) {
+    args = Array.prototype.slice.call(arguments, 1);
+    listeners = handler.slice();
+    len = listeners.length;
+    for (i = 0; i < len; i++)
+      listeners[i].apply(this, args);
+  }
+
+  return true;
+};
+
+EventEmitter.prototype.addListener = function(type, listener) {
+  var m;
+
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  if (!this._events)
+    this._events = {};
+
+  // To avoid recursion in the case that type === "newListener"! Before
+  // adding it to the listeners, first emit "newListener".
+  if (this._events.newListener)
+    this.emit('newListener', type,
+              isFunction(listener.listener) ?
+              listener.listener : listener);
+
+  if (!this._events[type])
+    // Optimize the case of one listener. Don't need the extra array object.
+    this._events[type] = listener;
+  else if (isObject(this._events[type]))
+    // If we've already got an array, just append.
+    this._events[type].push(listener);
+  else
+    // Adding the second element, need to change to array.
+    this._events[type] = [this._events[type], listener];
+
+  // Check for listener leak
+  if (isObject(this._events[type]) && !this._events[type].warned) {
+    if (!isUndefined(this._maxListeners)) {
+      m = this._maxListeners;
+    } else {
+      m = EventEmitter.defaultMaxListeners;
+    }
+
+    if (m && m > 0 && this._events[type].length > m) {
+      this._events[type].warned = true;
+      console.error('(node) warning: possible EventEmitter memory ' +
+                    'leak detected. %d listeners added. ' +
+                    'Use emitter.setMaxListeners() to increase limit.',
+                    this._events[type].length);
+      if (typeof console.trace === 'function') {
+        // not supported in IE 10
+        console.trace();
+      }
+    }
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener) {
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  var fired = false;
+
+  function g() {
+    this.removeListener(type, g);
+
+    if (!fired) {
+      fired = true;
+      listener.apply(this, arguments);
+    }
+  }
+
+  g.listener = listener;
+  this.on(type, g);
+
+  return this;
+};
+
+// emits a 'removeListener' event iff the listener was removed
+EventEmitter.prototype.removeListener = function(type, listener) {
+  var list, position, length, i;
+
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  if (!this._events || !this._events[type])
+    return this;
+
+  list = this._events[type];
+  length = list.length;
+  position = -1;
+
+  if (list === listener ||
+      (isFunction(list.listener) && list.listener === listener)) {
+    delete this._events[type];
+    if (this._events.removeListener)
+      this.emit('removeListener', type, listener);
+
+  } else if (isObject(list)) {
+    for (i = length; i-- > 0;) {
+      if (list[i] === listener ||
+          (list[i].listener && list[i].listener === listener)) {
+        position = i;
+        break;
+      }
+    }
+
+    if (position < 0)
+      return this;
+
+    if (list.length === 1) {
+      list.length = 0;
+      delete this._events[type];
+    } else {
+      list.splice(position, 1);
+    }
+
+    if (this._events.removeListener)
+      this.emit('removeListener', type, listener);
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+  var key, listeners;
+
+  if (!this._events)
+    return this;
+
+  // not listening for removeListener, no need to emit
+  if (!this._events.removeListener) {
+    if (arguments.length === 0)
+      this._events = {};
+    else if (this._events[type])
+      delete this._events[type];
+    return this;
+  }
+
+  // emit removeListener for all listeners on all events
+  if (arguments.length === 0) {
+    for (key in this._events) {
+      if (key === 'removeListener') continue;
+      this.removeAllListeners(key);
+    }
+    this.removeAllListeners('removeListener');
+    this._events = {};
+    return this;
+  }
+
+  listeners = this._events[type];
+
+  if (isFunction(listeners)) {
+    this.removeListener(type, listeners);
+  } else if (listeners) {
+    // LIFO order
+    while (listeners.length)
+      this.removeListener(type, listeners[listeners.length - 1]);
+  }
+  delete this._events[type];
+
+  return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+  var ret;
+  if (!this._events || !this._events[type])
+    ret = [];
+  else if (isFunction(this._events[type]))
+    ret = [this._events[type]];
+  else
+    ret = this._events[type].slice();
+  return ret;
+};
+
+EventEmitter.prototype.listenerCount = function(type) {
+  if (this._events) {
+    var evlistener = this._events[type];
+
+    if (isFunction(evlistener))
+      return 1;
+    else if (evlistener)
+      return evlistener.length;
+  }
+  return 0;
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+  return emitter.listenerCount(type);
+};
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+
+
+/***/ }),
+/* 17 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var nodes = __webpack_require__(3);
+
+var lib = __webpack_require__(0);
+
+var sym = 0;
+
+function gensym() {
+  return 'hole_' + sym++;
+} // copy-on-write version of map
+
+
+function mapCOW(arr, func) {
+  var res = null;
+
+  for (var i = 0; i < arr.length; i++) {
+    var item = func(arr[i]);
+
+    if (item !== arr[i]) {
+      if (!res) {
+        res = arr.slice();
+      }
+
+      res[i] = item;
+    }
+  }
+
+  return res || arr;
+}
+
+function walk(ast, func, depthFirst) {
+  if (!(ast instanceof nodes.Node)) {
+    return ast;
+  }
+
+  if (!depthFirst) {
+    var astT = func(ast);
+
+    if (astT && astT !== ast) {
+      return astT;
+    }
+  }
+
+  if (ast instanceof nodes.NodeList) {
+    var children = mapCOW(ast.children, function (node) {
+      return walk(node, func, depthFirst);
+    });
+
+    if (children !== ast.children) {
+      ast = new nodes[ast.typename](ast.lineno, ast.colno, children);
+    }
+  } else if (ast instanceof nodes.CallExtension) {
+    var args = walk(ast.args, func, depthFirst);
+    var contentArgs = mapCOW(ast.contentArgs, function (node) {
+      return walk(node, func, depthFirst);
+    });
+
+    if (args !== ast.args || contentArgs !== ast.contentArgs) {
+      ast = new nodes[ast.typename](ast.extName, ast.prop, args, contentArgs);
+    }
+  } else {
+    var props = ast.fields.map(function (field) {
+      return ast[field];
+    });
+    var propsT = mapCOW(props, function (prop) {
+      return walk(prop, func, depthFirst);
+    });
+
+    if (propsT !== props) {
+      ast = new nodes[ast.typename](ast.lineno, ast.colno);
+      propsT.forEach(function (prop, i) {
+        ast[ast.fields[i]] = prop;
+      });
+    }
+  }
+
+  return depthFirst ? func(ast) || ast : ast;
+}
+
+function depthWalk(ast, func) {
+  return walk(ast, func, true);
+}
+
+function _liftFilters(node, asyncFilters, prop) {
+  var children = [];
+  var walked = depthWalk(prop ? node[prop] : node, function (descNode) {
+    var symbol;
+
+    if (descNode instanceof nodes.Block) {
+      return descNode;
+    } else if (descNode instanceof nodes.Filter && lib.indexOf(asyncFilters, descNode.name.value) !== -1 || descNode instanceof nodes.CallExtensionAsync) {
+      symbol = new nodes.Symbol(descNode.lineno, descNode.colno, gensym());
+      children.push(new nodes.FilterAsync(descNode.lineno, descNode.colno, descNode.name, descNode.args, symbol));
+    }
+
+    return symbol;
+  });
+
+  if (prop) {
+    node[prop] = walked;
+  } else {
+    node = walked;
+  }
+
+  if (children.length) {
+    children.push(node);
+    return new nodes.NodeList(node.lineno, node.colno, children);
+  } else {
+    return node;
+  }
+}
+
+function liftFilters(ast, asyncFilters) {
+  return depthWalk(ast, function (node) {
+    if (node instanceof nodes.Output) {
+      return _liftFilters(node, asyncFilters);
+    } else if (node instanceof nodes.Set) {
+      return _liftFilters(node, asyncFilters, 'value');
+    } else if (node instanceof nodes.For) {
+      return _liftFilters(node, asyncFilters, 'arr');
+    } else if (node instanceof nodes.If) {
+      return _liftFilters(node, asyncFilters, 'cond');
+    } else if (node instanceof nodes.CallExtension) {
+      return _liftFilters(node, asyncFilters, 'args');
+    } else {
+      return undefined;
+    }
+  });
+}
+
+function liftSuper(ast) {
+  return walk(ast, function (blockNode) {
+    if (!(blockNode instanceof nodes.Block)) {
+      return;
+    }
+
+    var hasSuper = false;
+    var symbol = gensym();
+    blockNode.body = walk(blockNode.body, function (node) {
+      // eslint-disable-line consistent-return
+      if (node instanceof nodes.FunCall && node.name.value === 'super') {
+        hasSuper = true;
+        return new nodes.Symbol(node.lineno, node.colno, symbol);
+      }
+    });
+
+    if (hasSuper) {
+      blockNode.body.children.unshift(new nodes.Super(0, 0, blockNode.name, new nodes.Symbol(0, 0, symbol)));
+    }
+  });
+}
+
+function convertStatements(ast) {
+  return depthWalk(ast, function (node) {
+    if (!(node instanceof nodes.If) && !(node instanceof nodes.For)) {
+      return undefined;
+    }
+
+    var async = false;
+    walk(node, function (child) {
+      if (child instanceof nodes.FilterAsync || child instanceof nodes.IfAsync || child instanceof nodes.AsyncEach || child instanceof nodes.AsyncAll || child instanceof nodes.CallExtensionAsync) {
+        async = true; // Stop iterating by returning the node
+
+        return child;
+      }
+
+      return undefined;
+    });
+
+    if (async) {
+      if (node instanceof nodes.If) {
+        return new nodes.IfAsync(node.lineno, node.colno, node.cond, node.body, node.else_);
+      } else if (node instanceof nodes.For && !(node instanceof nodes.AsyncAll)) {
+        return new nodes.AsyncEach(node.lineno, node.colno, node.arr, node.name, node.body, node.else_);
+      }
+    }
+
+    return undefined;
+  });
+}
+
+function cps(ast, asyncFilters) {
+  return convertStatements(liftSuper(liftFilters(ast, asyncFilters)));
+}
+
+function transform(ast, asyncFilters) {
+  return cps(ast, asyncFilters || []);
+} // var parser = require('./parser');
+// var src = 'hello {% foo %}{% endfoo %} end';
+// var ast = transform(parser.parse(src, [new FooExtension()]), ['bar']);
+// nodes.printNodes(ast);
+
+
+module.exports = {
+  transform: transform
+};
+
+/***/ }),
+/* 18 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var lib = __webpack_require__(0);
+
+var r = __webpack_require__(2);
+
+var exports = module.exports = {};
+
+function normalize(value, defaultValue) {
+  if (value === null || value === undefined || value === false) {
+    return defaultValue;
+  }
+
+  return value;
+}
+
+exports.abs = Math.abs;
+
+function isNaN(num) {
+  return num !== num; // eslint-disable-line no-self-compare
+}
+
+function batch(arr, linecount, fillWith) {
+  var i;
+  var res = [];
+  var tmp = [];
+
+  for (i = 0; i < arr.length; i++) {
+    if (i % linecount === 0 && tmp.length) {
+      res.push(tmp);
+      tmp = [];
+    }
+
+    tmp.push(arr[i]);
+  }
+
+  if (tmp.length) {
+    if (fillWith) {
+      for (i = tmp.length; i < linecount; i++) {
+        tmp.push(fillWith);
+      }
+    }
+
+    res.push(tmp);
+  }
+
+  return res;
+}
+
+exports.batch = batch;
+
+function capitalize(str) {
+  str = normalize(str, '');
+  var ret = str.toLowerCase();
+  return r.copySafeness(str, ret.charAt(0).toUpperCase() + ret.slice(1));
+}
+
+exports.capitalize = capitalize;
+
+function center(str, width) {
+  str = normalize(str, '');
+  width = width || 80;
+
+  if (str.length >= width) {
+    return str;
+  }
+
+  var spaces = width - str.length;
+  var pre = lib.repeat(' ', spaces / 2 - spaces % 2);
+  var post = lib.repeat(' ', spaces / 2);
+  return r.copySafeness(str, pre + str + post);
+}
+
+exports.center = center;
+
+function default_(val, def, bool) {
+  if (bool) {
+    return val || def;
+  } else {
+    return val !== undefined ? val : def;
+  }
+} // TODO: it is confusing to export something called 'default'
+
+
+exports['default'] = default_; // eslint-disable-line dot-notation
+
+function dictsort(val, caseSensitive, by) {
+  if (!lib.isObject(val)) {
+    throw new lib.TemplateError('dictsort filter: val must be an object');
+  }
+
+  var array = []; // deliberately include properties from the object's prototype
+
+  for (var k in val) {
+    // eslint-disable-line guard-for-in, no-restricted-syntax
+    array.push([k, val[k]]);
+  }
+
+  var si;
+
+  if (by === undefined || by === 'key') {
+    si = 0;
+  } else if (by === 'value') {
+    si = 1;
+  } else {
+    throw new lib.TemplateError('dictsort filter: You can only sort by either key or value');
+  }
+
+  array.sort(function (t1, t2) {
+    var a = t1[si];
+    var b = t2[si];
+
+    if (!caseSensitive) {
+      if (lib.isString(a)) {
+        a = a.toUpperCase();
+      }
+
+      if (lib.isString(b)) {
+        b = b.toUpperCase();
+      }
+    }
+
+    return a > b ? 1 : a === b ? 0 : -1; // eslint-disable-line no-nested-ternary
+  });
+  return array;
+}
+
+exports.dictsort = dictsort;
+
+function dump(obj, spaces) {
+  return JSON.stringify(obj, null, spaces);
+}
+
+exports.dump = dump;
+
+function escape(str) {
+  if (str instanceof r.SafeString) {
+    return str;
+  }
+
+  str = str === null || str === undefined ? '' : str;
+  return r.markSafe(lib.escape(str.toString()));
+}
+
+exports.escape = escape;
+
+function safe(str) {
+  if (str instanceof r.SafeString) {
+    return str;
+  }
+
+  str = str === null || str === undefined ? '' : str;
+  return r.markSafe(str.toString());
+}
+
+exports.safe = safe;
+
+function first(arr) {
+  return arr[0];
+}
+
+exports.first = first;
+
+function forceescape(str) {
+  str = str === null || str === undefined ? '' : str;
+  return r.markSafe(lib.escape(str.toString()));
+}
+
+exports.forceescape = forceescape;
+
+function groupby(arr, attr) {
+  return lib.groupBy(arr, attr, this.env.opts.throwOnUndefined);
+}
+
+exports.groupby = groupby;
+
+function indent(str, width, indentfirst) {
+  str = normalize(str, '');
+
+  if (str === '') {
+    return '';
+  }
+
+  width = width || 4; // let res = '';
+
+  var lines = str.split('\n');
+  var sp = lib.repeat(' ', width);
+  var res = lines.map(function (l, i) {
+    return i === 0 && !indentfirst ? l : "" + sp + l;
+  }).join('\n');
+  return r.copySafeness(str, res);
+}
+
+exports.indent = indent;
+
+function join(arr, del, attr) {
+  del = del || '';
+
+  if (attr) {
+    arr = lib.map(arr, function (v) {
+      return v[attr];
+    });
+  }
+
+  return arr.join(del);
+}
+
+exports.join = join;
+
+function last(arr) {
+  return arr[arr.length - 1];
+}
+
+exports.last = last;
+
+function lengthFilter(val) {
+  var value = normalize(val, '');
+
+  if (value !== undefined) {
+    if (typeof Map === 'function' && value instanceof Map || typeof Set === 'function' && value instanceof Set) {
+      // ECMAScript 2015 Maps and Sets
+      return value.size;
+    }
+
+    if (lib.isObject(value) && !(value instanceof r.SafeString)) {
+      // Objects (besides SafeStrings), non-primative Arrays
+      return lib.keys(value).length;
+    }
+
+    return value.length;
+  }
+
+  return 0;
+}
+
+exports.length = lengthFilter;
+
+function list(val) {
+  if (lib.isString(val)) {
+    return val.split('');
+  } else if (lib.isObject(val)) {
+    return lib._entries(val || {}).map(function (_ref) {
+      var key = _ref[0],
+          value = _ref[1];
+      return {
+        key: key,
+        value: value
+      };
+    });
+  } else if (lib.isArray(val)) {
+    return val;
+  } else {
+    throw new lib.TemplateError('list filter: type not iterable');
+  }
+}
+
+exports.list = list;
+
+function lower(str) {
+  str = normalize(str, '');
+  return str.toLowerCase();
+}
+
+exports.lower = lower;
+
+function nl2br(str) {
+  if (str === null || str === undefined) {
+    return '';
+  }
+
+  return r.copySafeness(str, str.replace(/\r\n|\n/g, '<br />\n'));
+}
+
+exports.nl2br = nl2br;
+
+function random(arr) {
+  return arr[Math.floor(Math.random() * arr.length)];
+}
+
+exports.random = random;
+/**
+ * Construct select or reject filter
+ *
+ * @param {boolean} expectedTestResult
+ * @returns {function(array, string, *): array}
+ */
+
+function getSelectOrReject(expectedTestResult) {
+  function filter(arr, testName, secondArg) {
+    if (testName === void 0) {
+      testName = 'truthy';
+    }
+
+    var context = this;
+    var test = context.env.getTest(testName);
+    return lib.toArray(arr).filter(function examineTestResult(item) {
+      return test.call(context, item, secondArg) === expectedTestResult;
+    });
+  }
+
+  return filter;
+}
+
+exports.reject = getSelectOrReject(false);
+
+function rejectattr(arr, attr) {
+  return arr.filter(function (item) {
+    return !item[attr];
+  });
+}
+
+exports.rejectattr = rejectattr;
+exports.select = getSelectOrReject(true);
+
+function selectattr(arr, attr) {
+  return arr.filter(function (item) {
+    return !!item[attr];
+  });
+}
+
+exports.selectattr = selectattr;
+
+function replace(str, old, new_, maxCount) {
+  var originalStr = str;
+
+  if (old instanceof RegExp) {
+    return str.replace(old, new_);
+  }
+
+  if (typeof maxCount === 'undefined') {
+    maxCount = -1;
+  }
+
+  var res = ''; // Output
+  // Cast Numbers in the search term to string
+
+  if (typeof old === 'number') {
+    old = '' + old;
+  } else if (typeof old !== 'string') {
+    // If it is something other than number or string,
+    // return the original string
+    return str;
+  } // Cast numbers in the replacement to string
+
+
+  if (typeof str === 'number') {
+    str = '' + str;
+  } // If by now, we don't have a string, throw it back
+
+
+  if (typeof str !== 'string' && !(str instanceof r.SafeString)) {
+    return str;
+  } // ShortCircuits
+
+
+  if (old === '') {
+    // Mimic the python behaviour: empty string is replaced
+    // by replacement e.g. "abc"|replace("", ".") -> .a.b.c.
+    res = new_ + str.split('').join(new_) + new_;
+    return r.copySafeness(str, res);
+  }
+
+  var nextIndex = str.indexOf(old); // if # of replacements to perform is 0, or the string to does
+  // not contain the old value, return the string
+
+  if (maxCount === 0 || nextIndex === -1) {
+    return str;
+  }
+
+  var pos = 0;
+  var count = 0; // # of replacements made
+
+  while (nextIndex > -1 && (maxCount === -1 || count < maxCount)) {
+    // Grab the next chunk of src string and add it with the
+    // replacement, to the result
+    res += str.substring(pos, nextIndex) + new_; // Increment our pointer in the src string
+
+    pos = nextIndex + old.length;
+    count++; // See if there are any more replacements to be made
+
+    nextIndex = str.indexOf(old, pos);
+  } // We've either reached the end, or done the max # of
+  // replacements, tack on any remaining string
+
+
+  if (pos < str.length) {
+    res += str.substring(pos);
+  }
+
+  return r.copySafeness(originalStr, res);
+}
+
+exports.replace = replace;
+
+function reverse(val) {
+  var arr;
+
+  if (lib.isString(val)) {
+    arr = list(val);
+  } else {
+    // Copy it
+    arr = lib.map(val, function (v) {
+      return v;
+    });
+  }
+
+  arr.reverse();
+
+  if (lib.isString(val)) {
+    return r.copySafeness(val, arr.join(''));
+  }
+
+  return arr;
+}
+
+exports.reverse = reverse;
+
+function round(val, precision, method) {
+  precision = precision || 0;
+  var factor = Math.pow(10, precision);
+  var rounder;
+
+  if (method === 'ceil') {
+    rounder = Math.ceil;
+  } else if (method === 'floor') {
+    rounder = Math.floor;
+  } else {
+    rounder = Math.round;
+  }
+
+  return rounder(val * factor) / factor;
+}
+
+exports.round = round;
+
+function slice(arr, slices, fillWith) {
+  var sliceLength = Math.floor(arr.length / slices);
+  var extra = arr.length % slices;
+  var res = [];
+  var offset = 0;
+
+  for (var i = 0; i < slices; i++) {
+    var start = offset + i * sliceLength;
+
+    if (i < extra) {
+      offset++;
+    }
+
+    var end = offset + (i + 1) * sliceLength;
+    var currSlice = arr.slice(start, end);
+
+    if (fillWith && i >= extra) {
+      currSlice.push(fillWith);
+    }
+
+    res.push(currSlice);
+  }
+
+  return res;
+}
+
+exports.slice = slice;
+
+function sum(arr, attr, start) {
+  if (start === void 0) {
+    start = 0;
+  }
+
+  if (attr) {
+    arr = lib.map(arr, function (v) {
+      return v[attr];
+    });
+  }
+
+  return start + arr.reduce(function (a, b) {
+    return a + b;
+  }, 0);
+}
+
+exports.sum = sum;
+exports.sort = r.makeMacro(['value', 'reverse', 'case_sensitive', 'attribute'], [], function (arr, reversed, caseSens, attr) {
+  // Copy it
+  var array = lib.map(arr, function (v) {
+    return v;
+  });
+  array.sort(function (a, b) {
+    var x = attr ? a[attr] : a;
+    var y = attr ? b[attr] : b;
+
+    if (!caseSens && lib.isString(x) && lib.isString(y)) {
+      x = x.toLowerCase();
+      y = y.toLowerCase();
+    }
+
+    if (x < y) {
+      return reversed ? 1 : -1;
+    } else if (x > y) {
+      return reversed ? -1 : 1;
+    } else {
+      return 0;
+    }
+  });
+  return array;
+});
+
+function string(obj) {
+  return r.copySafeness(obj, obj);
+}
+
+exports.string = string;
+
+function striptags(input, preserveLinebreaks) {
+  input = normalize(input, '');
+  var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>|<!--[\s\S]*?-->/gi;
+  var trimmedInput = trim(input.replace(tags, ''));
+  var res = '';
+
+  if (preserveLinebreaks) {
+    res = trimmedInput.replace(/^ +| +$/gm, '') // remove leading and trailing spaces
+    .replace(/ +/g, ' ') // squash adjacent spaces
+    .replace(/(\r\n)/g, '\n') // normalize linebreaks (CRLF -> LF)
+    .replace(/\n\n\n+/g, '\n\n'); // squash abnormal adjacent linebreaks
+  } else {
+    res = trimmedInput.replace(/\s+/gi, ' ');
+  }
+
+  return r.copySafeness(input, res);
+}
+
+exports.striptags = striptags;
+
+function title(str) {
+  str = normalize(str, '');
+  var words = str.split(' ').map(function (word) {
+    return capitalize(word);
+  });
+  return r.copySafeness(str, words.join(' '));
+}
+
+exports.title = title;
+
+function trim(str) {
+  return r.copySafeness(str, str.replace(/^\s*|\s*$/g, ''));
+}
+
+exports.trim = trim;
+
+function truncate(input, length, killwords, end) {
+  var orig = input;
+  input = normalize(input, '');
+  length = length || 255;
+
+  if (input.length <= length) {
+    return input;
+  }
+
+  if (killwords) {
+    input = input.substring(0, length);
+  } else {
+    var idx = input.lastIndexOf(' ', length);
+
+    if (idx === -1) {
+      idx = length;
+    }
+
+    input = input.substring(0, idx);
+  }
+
+  input += end !== undefined && end !== null ? end : '...';
+  return r.copySafeness(orig, input);
+}
+
+exports.truncate = truncate;
+
+function upper(str) {
+  str = normalize(str, '');
+  return str.toUpperCase();
+}
+
+exports.upper = upper;
+
+function urlencode(obj) {
+  var enc = encodeURIComponent;
+
+  if (lib.isString(obj)) {
+    return enc(obj);
+  } else {
+    var keyvals = lib.isArray(obj) ? obj : lib._entries(obj);
+    return keyvals.map(function (_ref2) {
+      var k = _ref2[0],
+          v = _ref2[1];
+      return enc(k) + "=" + enc(v);
+    }).join('&');
+  }
+}
+
+exports.urlencode = urlencode; // For the jinja regexp, see
+// https://github.com/mitsuhiko/jinja2/blob/f15b814dcba6aa12bc74d1f7d0c881d55f7126be/jinja2/utils.py#L20-L23
+
+var puncRe = /^(?:\(|<|&lt;)?(.*?)(?:\.|,|\)|\n|&gt;)?$/; // from http://blog.gerv.net/2011/05/html5_email_address_regexp/
+
+var emailRe = /^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i;
+var httpHttpsRe = /^https?:\/\/.*$/;
+var wwwRe = /^www\./;
+var tldRe = /\.(?:org|net|com)(?:\:|\/|$)/;
+
+function urlize(str, length, nofollow) {
+  if (isNaN(length)) {
+    length = Infinity;
+  }
+
+  var noFollowAttr = nofollow === true ? ' rel="nofollow"' : '';
+  var words = str.split(/(\s+)/).filter(function (word) {
+    // If the word has no length, bail. This can happen for str with
+    // trailing whitespace.
+    return word && word.length;
+  }).map(function (word) {
+    var matches = word.match(puncRe);
+    var possibleUrl = matches ? matches[1] : word;
+    var shortUrl = possibleUrl.substr(0, length); // url that starts with http or https
+
+    if (httpHttpsRe.test(possibleUrl)) {
+      return "<a href=\"" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</a>";
+    } // url that starts with www.
+
+
+    if (wwwRe.test(possibleUrl)) {
+      return "<a href=\"http://" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</a>";
+    } // an email address of the form username@domain.tld
+
+
+    if (emailRe.test(possibleUrl)) {
+      return "<a href=\"mailto:" + possibleUrl + "\">" + possibleUrl + "</a>";
+    } // url that ends in .com, .org or .net that is not an email address
+
+
+    if (tldRe.test(possibleUrl)) {
+      return "<a href=\"http://" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</a>";
+    }
+
+    return word;
+  });
+  return words.join('');
+}
+
+exports.urlize = urlize;
+
+function wordcount(str) {
+  str = normalize(str, '');
+  var words = str ? str.match(/\w+/g) : null;
+  return words ? words.length : null;
+}
+
+exports.wordcount = wordcount;
+
+function float(val, def) {
+  var res = parseFloat(val);
+  return isNaN(res) ? def : res;
+}
+
+exports.float = float;
+
+function int(val, def) {
+  var res = parseInt(val, 10);
+  return isNaN(res) ? def : res;
+}
+
+exports.int = int; // Aliases
+
+exports.d = exports.default;
+exports.e = exports.escape;
+
+/***/ }),
+/* 19 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
+
+var Loader = __webpack_require__(6);
+
+var PrecompiledLoader =
+/*#__PURE__*/
+function (_Loader) {
+  _inheritsLoose(PrecompiledLoader, _Loader);
+
+  function PrecompiledLoader(compiledTemplates) {
+    var _this;
+
+    _this = _Loader.call(this) || this;
+    _this.precompiled = compiledTemplates || {};
+    return _this;
+  }
+
+  var _proto = PrecompiledLoader.prototype;
+
+  _proto.getSource = function getSource(name) {
+    if (this.precompiled[name]) {
+      return {
+        src: {
+          type: 'code',
+          obj: this.precompiled[name]
+        },
+        path: name
+      };
+    }
+
+    return null;
+  };
+
+  return PrecompiledLoader;
+}(Loader);
+
+module.exports = {
+  PrecompiledLoader: PrecompiledLoader
+};
+
+/***/ }),
+/* 20 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var SafeString = __webpack_require__(2).SafeString;
+/**
+ * Returns `true` if the object is a function, otherwise `false`.
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+
+function callable(value) {
+  return typeof value === 'function';
+}
+
+exports.callable = callable;
+/**
+ * Returns `true` if the object is strictly not `undefined`.
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function defined(value) {
+  return value !== undefined;
+}
+
+exports.defined = defined;
+/**
+ * Returns `true` if the operand (one) is divisble by the test's argument
+ * (two).
+ * @param { number } one
+ * @param { number } two
+ * @returns { boolean }
+ */
+
+function divisibleby(one, two) {
+  return one % two === 0;
+}
+
+exports.divisibleby = divisibleby;
+/**
+ * Returns true if the string has been escaped (i.e., is a SafeString).
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function escaped(value) {
+  return value instanceof SafeString;
+}
+
+exports.escaped = escaped;
+/**
+ * Returns `true` if the arguments are strictly equal.
+ * @param { any } one
+ * @param { any } two
+ */
+
+function equalto(one, two) {
+  return one === two;
+}
+
+exports.equalto = equalto; // Aliases
+
+exports.eq = exports.equalto;
+exports.sameas = exports.equalto;
+/**
+ * Returns `true` if the value is evenly divisible by 2.
+ * @param { number } value
+ * @returns { boolean }
+ */
+
+function even(value) {
+  return value % 2 === 0;
+}
+
+exports.even = even;
+/**
+ * Returns `true` if the value is falsy - if I recall correctly, '', 0, false,
+ * undefined, NaN or null. I don't know if we should stick to the default JS
+ * behavior or attempt to replicate what Python believes should be falsy (i.e.,
+ * empty arrays, empty dicts, not 0...).
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function falsy(value) {
+  return !value;
+}
+
+exports.falsy = falsy;
+/**
+ * Returns `true` if the operand (one) is greater or equal to the test's
+ * argument (two).
+ * @param { number } one
+ * @param { number } two
+ * @returns { boolean }
+ */
+
+function ge(one, two) {
+  return one >= two;
+}
+
+exports.ge = ge;
+/**
+ * Returns `true` if the operand (one) is greater than the test's argument
+ * (two).
+ * @param { number } one
+ * @param { number } two
+ * @returns { boolean }
+ */
+
+function greaterthan(one, two) {
+  return one > two;
+}
+
+exports.greaterthan = greaterthan; // alias
+
+exports.gt = exports.greaterthan;
+/**
+ * Returns `true` if the operand (one) is less than or equal to the test's
+ * argument (two).
+ * @param { number } one
+ * @param { number } two
+ * @returns { boolean }
+ */
+
+function le(one, two) {
+  return one <= two;
+}
+
+exports.le = le;
+/**
+ * Returns `true` if the operand (one) is less than the test's passed argument
+ * (two).
+ * @param { number } one
+ * @param { number } two
+ * @returns { boolean }
+ */
+
+function lessthan(one, two) {
+  return one < two;
+}
+
+exports.lessthan = lessthan; // alias
+
+exports.lt = exports.lessthan;
+/**
+ * Returns `true` if the string is lowercased.
+ * @param { string } value
+ * @returns { boolean }
+ */
+
+function lower(value) {
+  return value.toLowerCase() === value;
+}
+
+exports.lower = lower;
+/**
+ * Returns `true` if the operand (one) is less than or equal to the test's
+ * argument (two).
+ * @param { number } one
+ * @param { number } two
+ * @returns { boolean }
+ */
+
+function ne(one, two) {
+  return one !== two;
+}
+
+exports.ne = ne;
+/**
+ * Returns true if the value is strictly equal to `null`.
+ * @param { any }
+ * @returns { boolean }
+ */
+
+function nullTest(value) {
+  return value === null;
+}
+
+exports.null = nullTest;
+/**
+ * Returns true if value is a number.
+ * @param { any }
+ * @returns { boolean }
+ */
+
+function number(value) {
+  return typeof value === 'number';
+}
+
+exports.number = number;
+/**
+ * Returns `true` if the value is *not* evenly divisible by 2.
+ * @param { number } value
+ * @returns { boolean }
+ */
+
+function odd(value) {
+  return value % 2 === 1;
+}
+
+exports.odd = odd;
+/**
+ * Returns `true` if the value is a string, `false` if not.
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function string(value) {
+  return typeof value === 'string';
+}
+
+exports.string = string;
+/**
+ * Returns `true` if the value is not in the list of things considered falsy:
+ * '', null, undefined, 0, NaN and false.
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function truthy(value) {
+  return !!value;
+}
+
+exports.truthy = truthy;
+/**
+ * Returns `true` if the value is undefined.
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function undefinedTest(value) {
+  return value === undefined;
+}
+
+exports.undefined = undefinedTest;
+/**
+ * Returns `true` if the string is uppercased.
+ * @param { string } value
+ * @returns { boolean }
+ */
+
+function upper(value) {
+  return value.toUpperCase() === value;
+}
+
+exports.upper = upper;
+/**
+ * If ES6 features are available, returns `true` if the value implements the
+ * `Symbol.iterator` method. If not, it's a string or Array.
+ *
+ * Could potentially cause issues if a browser exists that has Set and Map but
+ * not Symbol.
+ *
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function iterable(value) {
+  if (typeof Symbol !== 'undefined') {
+    return !!value[Symbol.iterator];
+  } else {
+    return Array.isArray(value) || typeof value === 'string';
+  }
+}
+
+exports.iterable = iterable;
+/**
+ * If ES6 features are available, returns `true` if the value is an object hash
+ * or an ES6 Map. Otherwise just return if it's an object hash.
+ * @param { any } value
+ * @returns { boolean }
+ */
+
+function mapping(value) {
+  // only maps and object hashes
+  var bool = value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value);
+
+  if (Set) {
+    return bool && !(value instanceof Set);
+  } else {
+    return bool;
+  }
+}
+
+exports.mapping = mapping;
+
+/***/ }),
+/* 21 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _cycler(items) {
+  var index = -1;
+  return {
+    current: null,
+    reset: function reset() {
+      index = -1;
+      this.current = null;
+    },
+    next: function next() {
+      index++;
+
+      if (index >= items.length) {
+        index = 0;
+      }
+
+      this.current = items[index];
+      return this.current;
+    }
+  };
+}
+
+function _joiner(sep) {
+  sep = sep || ',';
+  var first = true;
+  return function () {
+    var val = first ? '' : sep;
+    first = false;
+    return val;
+  };
+} // Making this a function instead so it returns a new object
+// each time it's called. That way, if something like an environment
+// uses it, they will each have their own copy.
+
+
+function globals() {
+  return {
+    range: function range(start, stop, step) {
+      if (typeof stop === 'undefined') {
+        stop = start;
+        start = 0;
+        step = 1;
+      } else if (!step) {
+        step = 1;
+      }
+
+      var arr = [];
+
+      if (step > 0) {
+        for (var i = start; i < stop; i += step) {
+          arr.push(i);
+        }
+      } else {
+        for (var _i = start; _i > stop; _i += step) {
+          // eslint-disable-line for-direction
+          arr.push(_i);
+        }
+      }
+
+      return arr;
+    },
+    cycler: function cycler() {
+      return _cycler(Array.prototype.slice.call(arguments));
+    },
+    joiner: function joiner(sep) {
+      return _joiner(sep);
+    }
+  };
+}
+
+module.exports = globals;
+
+/***/ }),
+/* 22 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var path = __webpack_require__(4);
+
+module.exports = function express(env, app) {
+  function NunjucksView(name, opts) {
+    this.name = name;
+    this.path = name;
+    this.defaultEngine = opts.defaultEngine;
+    this.ext = path.extname(name);
+
+    if (!this.ext && !this.defaultEngine) {
+      throw new Error('No default engine was specified and no extension was provided.');
+    }
+
+    if (!this.ext) {
+      this.name += this.ext = (this.defaultEngine[0] !== '.' ? '.' : '') + this.defaultEngine;
+    }
+  }
+
+  NunjucksView.prototype.render = function render(opts, cb) {
+    env.render(this.name, opts, cb);
+  };
+
+  app.set('view', NunjucksView);
+  app.set('nunjucksEnv', env);
+  return env;
+};
+
+/***/ }),
+/* 23 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var fs = __webpack_require__(4);
+
+var path = __webpack_require__(4);
+
+var _require = __webpack_require__(0),
+    _prettifyError = _require._prettifyError;
+
+var compiler = __webpack_require__(5);
+
+var _require2 = __webpack_require__(7),
+    Environment = _require2.Environment;
+
+var precompileGlobal = __webpack_require__(24);
+
+function match(filename, patterns) {
+  if (!Array.isArray(patterns)) {
+    return false;
+  }
+
+  return patterns.some(function (pattern) {
+    return filename.match(pattern);
+  });
+}
+
+function precompileString(str, opts) {
+  opts = opts || {};
+  opts.isString = true;
+  var env = opts.env || new Environment([]);
+  var wrapper = opts.wrapper || precompileGlobal;
+
+  if (!opts.name) {
+    throw new Error('the "name" option is required when compiling a string');
+  }
+
+  return wrapper([_precompile(str, opts.name, env)], opts);
+}
+
+function precompile(input, opts) {
+  // The following options are available:
+  //
+  // * name: name of the template (auto-generated when compiling a directory)
+  // * isString: input is a string, not a file path
+  // * asFunction: generate a callable function
+  // * force: keep compiling on error
+  // * env: the Environment to use (gets extensions and async filters from it)
+  // * include: which file/folders to include (folders are auto-included, files are auto-excluded)
+  // * exclude: which file/folders to exclude (folders are auto-included, files are auto-excluded)
+  // * wrapper: function(templates, opts) {...}
+  //       Customize the output format to store the compiled template.
+  //       By default, templates are stored in a global variable used by the runtime.
+  //       A custom loader will be necessary to load your custom wrapper.
+  opts = opts || {};
+  var env = opts.env || new Environment([]);
+  var wrapper = opts.wrapper || precompileGlobal;
+
+  if (opts.isString) {
+    return precompileString(input, opts);
+  }
+
+  var pathStats = fs.existsSync(input) && fs.statSync(input);
+  var precompiled = [];
+  var templates = [];
+
+  function addTemplates(dir) {
+    fs.readdirSync(dir).forEach(function (file) {
+      var filepath = path.join(dir, file);
+      var subpath = filepath.substr(path.join(input, '/').length);
+      var stat = fs.statSync(filepath);
+
+      if (stat && stat.isDirectory()) {
+        subpath += '/';
+
+        if (!match(subpath, opts.exclude)) {
+          addTemplates(filepath);
+        }
+      } else if (match(subpath, opts.include)) {
+        templates.push(filepath);
+      }
+    });
+  }
+
+  if (pathStats.isFile()) {
+    precompiled.push(_precompile(fs.readFileSync(input, 'utf-8'), opts.name || input, env));
+  } else if (pathStats.isDirectory()) {
+    addTemplates(input);
+
+    for (var i = 0; i < templates.length; i++) {
+      var name = templates[i].replace(path.join(input, '/'), '');
+
+      try {
+        precompiled.push(_precompile(fs.readFileSync(templates[i], 'utf-8'), name, env));
+      } catch (e) {
+        if (opts.force) {
+          // Don't stop generating the output if we're
+          // forcing compilation.
+          console.error(e); // eslint-disable-line no-console
+        } else {
+          throw e;
+        }
+      }
+    }
+  }
+
+  return wrapper(precompiled, opts);
+}
+
+function _precompile(str, name, env) {
+  env = env || new Environment([]);
+  var asyncFilters = env.asyncFilters;
+  var extensions = env.extensionsList;
+  var template;
+  name = name.replace(/\\/g, '/');
+
+  try {
+    template = compiler.compile(str, asyncFilters, extensions, name, env.opts);
+  } catch (err) {
+    throw _prettifyError(name, false, err);
+  }
+
+  return {
+    name: name,
+    template: template
+  };
+}
+
+module.exports = {
+  precompile: precompile,
+  precompileString: precompileString
+};
+
+/***/ }),
+/* 24 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function precompileGlobal(templates, opts) {
+  var out = '';
+  opts = opts || {};
+
+  for (var i = 0; i < templates.length; i++) {
+    var name = JSON.stringify(templates[i].name);
+    var template = templates[i].template;
+    out += '(function() {' + '(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})' + '[' + name + '] = (function() {\n' + template + '\n})();\n';
+
+    if (opts.asFunction) {
+      out += 'return function(ctx, cb) { return nunjucks.render(' + name + ', ctx, cb); }\n';
+    }
+
+    out += '})();\n';
+  }
+
+  return out;
+}
+
+module.exports = precompileGlobal;
+
+/***/ }),
+/* 25 */
+/***/ (function(module, exports, __webpack_require__) {
+
+function installCompat() {
+  'use strict';
+  /* eslint-disable camelcase */
+  // This must be called like `nunjucks.installCompat` so that `this`
+  // references the nunjucks instance
+
+  var runtime = this.runtime;
+  var lib = this.lib; // Handle slim case where these 'modules' are excluded from the built source
+
+  var Compiler = this.compiler.Compiler;
+  var Parser = this.parser.Parser;
+  var nodes = this.nodes;
+  var lexer = this.lexer;
+  var orig_contextOrFrameLookup = runtime.contextOrFrameLookup;
+  var orig_memberLookup = runtime.memberLookup;
+  var orig_Compiler_assertType;
+  var orig_Parser_parseAggregate;
+
+  if (Compiler) {
+    orig_Compiler_assertType = Compiler.prototype.assertType;
+  }
+
+  if (Parser) {
+    orig_Parser_parseAggregate = Parser.prototype.parseAggregate;
+  }
+
+  function uninstall() {
+    runtime.contextOrFrameLookup = orig_contextOrFrameLookup;
+    runtime.memberLookup = orig_memberLookup;
+
+    if (Compiler) {
+      Compiler.prototype.assertType = orig_Compiler_assertType;
+    }
+
+    if (Parser) {
+      Parser.prototype.parseAggregate = orig_Parser_parseAggregate;
+    }
+  }
+
+  runtime.contextOrFrameLookup = function contextOrFrameLookup(context, frame, key) {
+    var val = orig_contextOrFrameLookup.apply(this, arguments);
+
+    if (val !== undefined) {
+      return val;
+    }
+
+    switch (key) {
+      case 'True':
+        return true;
+
+      case 'False':
+        return false;
+
+      case 'None':
+        return null;
+
+      default:
+        return undefined;
+    }
+  };
+
+  function getTokensState(tokens) {
+    return {
+      index: tokens.index,
+      lineno: tokens.lineno,
+      colno: tokens.colno
+    };
+  }
+
+  if ("STD" !== 'SLIM' && nodes && Compiler && Parser) {
+    // i.e., not slim mode
+    var Slice = nodes.Node.extend('Slice', {
+      fields: ['start', 'stop', 'step'],
+      init: function init(lineno, colno, start, stop, step) {
+        start = start || new nodes.Literal(lineno, colno, null);
+        stop = stop || new nodes.Literal(lineno, colno, null);
+        step = step || new nodes.Literal(lineno, colno, 1);
+        this.parent(lineno, colno, start, stop, step);
+      }
+    });
+
+    Compiler.prototype.assertType = function assertType(node) {
+      if (node instanceof Slice) {
+        return;
+      }
+
+      orig_Compiler_assertType.apply(this, arguments);
+    };
+
+    Compiler.prototype.compileSlice = function compileSlice(node, frame) {
+      this._emit('(');
+
+      this._compileExpression(node.start, frame);
+
+      this._emit('),(');
+
+      this._compileExpression(node.stop, frame);
+
+      this._emit('),(');
+
+      this._compileExpression(node.step, frame);
+
+      this._emit(')');
+    };
+
+    Parser.prototype.parseAggregate = function parseAggregate() {
+      var _this = this;
+
+      var origState = getTokensState(this.tokens); // Set back one accounting for opening bracket/parens
+
+      origState.colno--;
+      origState.index--;
+
+      try {
+        return orig_Parser_parseAggregate.apply(this);
+      } catch (e) {
+        var errState = getTokensState(this.tokens);
+
+        var rethrow = function rethrow() {
+          lib._assign(_this.tokens, errState);
+
+          return e;
+        }; // Reset to state before original parseAggregate called
+
+
+        lib._assign(this.tokens, origState);
+
+        this.peeked = false;
+        var tok = this.peekToken();
+
+        if (tok.type !== lexer.TOKEN_LEFT_BRACKET) {
+          throw rethrow();
+        } else {
+          this.nextToken();
+        }
+
+        var node = new Slice(tok.lineno, tok.colno); // If we don't encounter a colon while parsing, this is not a slice,
+        // so re-raise the original exception.
+
+        var isSlice = false;
+
+        for (var i = 0; i <= node.fields.length; i++) {
+          if (this.skip(lexer.TOKEN_RIGHT_BRACKET)) {
+            break;
+          }
+
+          if (i === node.fields.length) {
+            if (isSlice) {
+              this.fail('parseSlice: too many slice components', tok.lineno, tok.colno);
+            } else {
+              break;
+            }
+          }
+
+          if (this.skip(lexer.TOKEN_COLON)) {
+            isSlice = true;
+          } else {
+            var field = node.fields[i];
+            node[field] = this.parseExpression();
+            isSlice = this.skip(lexer.TOKEN_COLON) || isSlice;
+          }
+        }
+
+        if (!isSlice) {
+          throw rethrow();
+        }
+
+        return new nodes.Array(tok.lineno, tok.colno, [node]);
+      }
+    };
+  }
+
+  function sliceLookup(obj, start, stop, step) {
+    obj = obj || [];
+
+    if (start === null) {
+      start = step < 0 ? obj.length - 1 : 0;
+    }
+
+    if (stop === null) {
+      stop = step < 0 ? -1 : obj.length;
+    } else if (stop < 0) {
+      stop += obj.length;
+    }
+
+    if (start < 0) {
+      start += obj.length;
+    }
+
+    var results = [];
+
+    for (var i = start;; i += step) {
+      if (i < 0 || i > obj.length) {
+        break;
+      }
+
+      if (step > 0 && i >= stop) {
+        break;
+      }
+
+      if (step < 0 && i <= stop) {
+        break;
+      }
+
+      results.push(runtime.memberLookup(obj, i));
+    }
+
+    return results;
+  }
+
+  function hasOwnProp(obj, key) {
+    return Object.prototype.hasOwnProperty.call(obj, key);
+  }
+
+  var ARRAY_MEMBERS = {
+    pop: function pop(index) {
+      if (index === undefined) {
+        return this.pop();
+      }
+
+      if (index >= this.length || index < 0) {
+        throw new Error('KeyError');
+      }
+
+      return this.splice(index, 1);
+    },
+    append: function append(element) {
+      return this.push(element);
+    },
+    remove: function remove(element) {
+      for (var i = 0; i < this.length; i++) {
+        if (this[i] === element) {
+          return this.splice(i, 1);
+        }
+      }
+
+      throw new Error('ValueError');
+    },
+    count: function count(element) {
+      var count = 0;
+
+      for (var i = 0; i < this.length; i++) {
+        if (this[i] === element) {
+          count++;
+        }
+      }
+
+      return count;
+    },
+    index: function index(element) {
+      var i;
+
+      if ((i = this.indexOf(element)) === -1) {
+        throw new Error('ValueError');
+      }
+
+      return i;
+    },
+    find: function find(element) {
+      return this.indexOf(element);
+    },
+    insert: function insert(index, elem) {
+      return this.splice(index, 0, elem);
+    }
+  };
+  var OBJECT_MEMBERS = {
+    items: function items() {
+      return lib._entries(this);
+    },
+    values: function values() {
+      return lib._values(this);
+    },
+    keys: function keys() {
+      return lib.keys(this);
+    },
+    get: function get(key, def) {
+      var output = this[key];
+
+      if (output === undefined) {
+        output = def;
+      }
+
+      return output;
+    },
+    has_key: function has_key(key) {
+      return hasOwnProp(this, key);
+    },
+    pop: function pop(key, def) {
+      var output = this[key];
+
+      if (output === undefined && def !== undefined) {
+        output = def;
+      } else if (output === undefined) {
+        throw new Error('KeyError');
+      } else {
+        delete this[key];
+      }
+
+      return output;
+    },
+    popitem: function popitem() {
+      var keys = lib.keys(this);
+
+      if (!keys.length) {
+        throw new Error('KeyError');
+      }
+
+      var k = keys[0];
+      var val = this[k];
+      delete this[k];
+      return [k, val];
+    },
+    setdefault: function setdefault(key, def) {
+      if (def === void 0) {
+        def = null;
+      }
+
+      if (!(key in this)) {
+        this[key] = def;
+      }
+
+      return this[key];
+    },
+    update: function update(kwargs) {
+      lib._assign(this, kwargs);
+
+      return null; // Always returns None
+    }
+  };
+  OBJECT_MEMBERS.iteritems = OBJECT_MEMBERS.items;
+  OBJECT_MEMBERS.itervalues = OBJECT_MEMBERS.values;
+  OBJECT_MEMBERS.iterkeys = OBJECT_MEMBERS.keys;
+
+  runtime.memberLookup = function memberLookup(obj, val, autoescape) {
+    if (arguments.length === 4) {
+      return sliceLookup.apply(this, arguments);
+    }
+
+    obj = obj || {}; // If the object is an object, return any of the methods that Python would
+    // otherwise provide.
+
+    if (lib.isArray(obj) && hasOwnProp(ARRAY_MEMBERS, val)) {
+      return ARRAY_MEMBERS[val].bind(obj);
+    }
+
+    if (lib.isObject(obj) && hasOwnProp(OBJECT_MEMBERS, val)) {
+      return OBJECT_MEMBERS[val].bind(obj);
+    }
+
+    return orig_memberLookup.apply(this, arguments);
+  };
+
+  return uninstall;
+}
+
+module.exports = installCompat;
+
+/***/ })
+/******/ ]);
+});
+//# sourceMappingURL=nunjucks.js.map
\ No newline at end of file
diff --git a/docs/validator.html b/docs/validator.html
new file mode 100644
index 00000000..67b6bc52
--- /dev/null
+++ b/docs/validator.html
@@ -0,0 +1,141 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <!-- Required meta tags -->
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <!-- Bootstrap CSS -->
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
+
+    <!-- Handlebars -->
+    <script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
+
+    <title>PEP web validator</title>
+    <script id="entry-template" type="text/x-handlebars-template">
+    <div class="col-12">
+      Results for: {{ name }}<br>
+      Files included: {{ filenames }}<br>
+      <table class="table">
+        <thead>
+          <tr>
+            <th>Schema identifier</th>
+            <th>Valid?</th>
+            <th>Schema links</th>
+          </tr>
+        </thead>
+        <tbody>
+      {{#each validations }}
+          <tr>
+            <td>{{ this.name }} ({{ this.id }})</td>
+            <td>{{#if this.result }}<img src="img/no.png" alt="NO"  width="25"> {{ this.result }}{{else}}<img src="img/yes.png" alt="YES" width="25">{{/if}}</td>
+            <td><a href="{{this.docs}}">docs</a> | <a href="{{this.schema}}">schema</a></td>
+          </tr>
+      {{/each}}
+        </tbody>
+      </table>
+    </div>
+    </script>
+    <script>
+      const server = 'http://localhost:8000'
+      var source   = document.getElementById("entry-template").innerHTML;
+      var template = Handlebars.compile(source);
+
+      const validate = async (e, form) => {
+        e.preventDefault()
+        showSpinner("Validating...")
+        let formData = new FormData(form);
+        console.log(e)
+        for (var value of formData.values()) {
+         console.log(value);
+        }
+        const res = await fetch(server + '/validate', {
+          method:'POST',
+          body: formData,
+        })
+        const resjson = await res.json()
+        console.log(resjson)
+        showResults(resjson, template)
+        hideSpinner("Validation complete.")
+      }
+
+      function hideSpinner(message) {
+        document.getElementById('notices').innerHTML = message       
+        document.getElementById('spinner').style.visibility = 'hidden';
+      } 
+
+      function showSpinner(message) {
+        document.getElementById('spinner').style.visibility = 'visible';
+        document.getElementById('notices').innerHTML = message
+      } 
+
+      function showResults(data, template) {
+        var html = template(data)
+        document.getElementById('results').innerHTML = html
+      } 
+
+      // Confirm that the server is ready to validate
+      const checkStatus = async () => {
+        const res = await fetch(server + '/status', {
+          method:'GET'
+        })
+        const resjson = await res.json()
+        console.log(resjson)
+
+        if (resjson.status != "OK") {
+          document.getElementById('vbutton').disabled = true
+          hideSpinner("Validation server is down, please try later.")
+        }
+      }
+
+    </script>
+  </head>
+  <body onload="checkStatus();">
+
+  <div class="container">
+    <header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
+      <a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
+        <img src="img/eido.svg" alt="eido" width="180" height="45">
+        <span class="fs-4">Web validator</span>
+      </a>
+
+      <ul class="nav nav-pills">
+        <li class="nav-item"><a href="#" class="nav-link">Home</a></li>
+        <li class="nav-item"><a href="https://eido.databio.org" class="nav-link">Eido documentation</a></li>
+        <li class="nav-item"><a href="https://pep.databio.org" class="nav-link">PEP Documentation</a></li>
+      </ul>
+    </header>
+  </div>
+  <div class="container">
+      <p>This tool will validate your sample metadata against one or more schemas. Drag and drop all parts of your PEP here. This includes metadata only, which is the config YAML file, any sample or subsample table CSV files, etc. Then, click "Validate". </p>
+      <form enctype="multipart/form-data" method="post" onsubmit="validate(event,this);" action="#">
+      <div class="card mt-3">
+      <h5 class="card-header">File Upload</h5>
+      <div class=" card-body">
+        <div class="row">
+        <div class="col">
+        <input name="files" type="file" multiple>
+        </div>
+        <div class="col">
+        <input type="submit" value="Validate" id="vbutton">
+        <span id="spinner" class="spinner-border spinner-border-sm p-1 m-1" role="status" aria-hidden="true" style="visibility: hidden"></span>
+        <span id="notices" class="m-3"></span>  
+        </div>
+      </div></div></div><!-- Row -->
+      </form>
+    <div class="card mt-3 mb-3">
+      <h5 class="card-header">Results</h5>
+      <div class="card-body"  id="results">
+        No results to display.
+      </div>
+    </div>
+
+    <p class="mb-0 text-muted">Want API access? This tool is a static, client-hosted form that accesses an API validator service based on <a href="https://peppy.databio.org">peppy</a>. You can also access this service programatically if you want to validate sample metadata as part of a pipeline or other tool.</p>
+
+    <footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
+    <p class="col-md-4 mb-0 text-muted">© 2021 <a href="http://databio.org">Sheffield Computational Biology Lab</a></p>
+  </footer>
+     </div><!-- Container -->
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
+  </body>
+</html>
diff --git a/docs/writing-a-filter.md b/docs/writing-a-filter.md
index 9793a878..87f91180 100644
--- a/docs/writing-a-filter.md
+++ b/docs/writing-a-filter.md
@@ -1,12 +1,12 @@
-**The PEP filters are an experimental feature and may change in feature versions of `eido`**
+**Filters are an experimental feature and may change in feature versions of `eido`**
 
 # How to write a custom eido filter
 
-One of `eido`'s tasks is to provide a CLI to convert a PEP into alternative formats. These include some built-in formats, `like csv` (which spits out a processed `csv` file, with project/sample modified), `yaml`, and a few others. It also provides a plugin system so that you can write your own Python functions to provide custom output formats.
+One of `eido`'s tasks is to provide a CLI to convert a PEP into alternative formats. These include some built-in formats, like `csv` (which spits out a processed `csv` file, with project/sample modified), `yaml`, and a few others. It also provides a plugin system so that you can write your own Python functions to provide custom output formats.
 
 ## Custom filters
 
-To write a custom filter, start by writing a Python package. You will need to write a function that takes a `peppy.Project` object as input, and prints out the custom file format. The filter functions also can require additional keyword arguments.
+To write a custom filter, start by writing a Python package. You will need to include a function that takes a `peppy.Project` object as input, and prints out the custom file format. The filter functions also can require additional keyword arguments.
 
 ### 1. Write functions to call
 
@@ -25,11 +25,9 @@ def my_custom_filter(p, **kwargs):
         out = re.sub('\n', '\n  ', yaml.safe_dump(s.to_dict(), default_flow_style=False))
         sys.stdout.write(out + "\n")
 ```
-For reference you can check the signatures of the functions in [Built-in `eido` Plugins Documentation](plugin_api_docs.md).
+For reference you can check the signatures of the functions in [Built-in `eido` Plugins Documentation](plugin_api_docs.md). Importantly, if the function *requires* any arguments (always provided via `**kwargs`), the creator of the function should take care of handling missing/faulty input.
 
-Importantly, if the function *requires* any arguments (always provided via `**kwargs`), the creator of the function should take care of handling missing/faulty input.
-
-Then, we need to link that function in to the `eido` filter plugin system.
+Next, we need to link that function in to the `eido` filter plugin system.
 
 ### 2. Add entry_points to setup.py
 
@@ -51,3 +49,7 @@ The format is: `'pep.filters': 'FILTER_NAME=PLUGIN_PACKAGE_NAME:FUNCTION_NAME'`.
 - "FILTER_NAME" can be any unique identifier for your plugin
 - "PLUGIN_PACKAGE_NAME" must be the name of python package the holds your plugin.
 - "FUNCTION_NAME" must match the name of the function in your package
+
+### 3. Install package
+
+If you install this package, any filters provided by it will be available for use with eido, which you can see using `eido filters`.
diff --git a/docs/writing-a-schema.md b/docs/writing-a-schema.md
index 38e88a05..1f713629 100644
--- a/docs/writing-a-schema.md
+++ b/docs/writing-a-schema.md
@@ -9,11 +9,11 @@ One of the features added by `eido` is the `imports` attribute. This allows you
 
 ```yaml
 description: A example schema for a pipeline.
-imports: 
+imports:
   - http://schema.databio.org/pep/2.0.0.yaml
 ```
 
-You can also use the `imports` to build other schemas that subclass your own schemas. 
+You can also use the `imports` to build other schemas that subclass your own schemas.
 
 ## Project and sample sections
 
@@ -22,7 +22,7 @@ Like the PEP itself, the schema is divided into two sections, one for the projec
 
 ```yaml
 description: A example schema for a pipeline.
-imports: 
+imports:
   - http://schema.databio.org/pep/2.0.0.yaml
 properties:
   config:
@@ -41,7 +41,7 @@ Let's say you're writing a PEP-compatible tool that requires 3 arguments: `read1
 
 ```yaml
 description: A example schema for a pipeline.
-imports: 
+imports:
   - http://schema.databio.org/pep/2.0.0.yaml
 properties:
   samples:
@@ -57,7 +57,7 @@ properties:
           description: "Fastq file for read 2"
         genome:
           type: string
-          description: "Refgenie genome registry identifier"          
+          description: "Refgenie genome registry identifier"
         read_length:
           type: integer
           description: "Length of the Unique Molecular Identifier, if any"
@@ -73,7 +73,7 @@ This document defines the required an optional sample attributes for this pipeli
 
 ## Required input files
 
-In the above example, we listed `read1` and `read2` attributes as *required*. This will enforce that these attributes must be defined on the samples, but for this example, this is not enough -- these also must *point to files that exist*. Checking for files is outside the scope of JSON Schema, which only validates JSON documents, so eido extends JSON Schema with the ability to specify which attributes should point to files. 
+In the above example, we listed `read1` and `read2` attributes as *required*. This will enforce that these attributes must be defined on the samples, but for this example, this is not enough -- these also must *point to files that exist*. Checking for files is outside the scope of JSON Schema, which only validates JSON documents, so eido extends JSON Schema with the ability to specify which attributes should point to files.
 
 Eido provides two ways to do it: `files` and `required_files`. The basic `files` is simply used to specify which attributes point to files, which are not required to exist. This is useful for tools that want to calculate the total size of any provided inputs, for example. The `required_files` list specifies that the attributes point to files that *must exist*, otherwise the PEP doesn't validate. Here's an example of specifying an optional and required input attribute:
 
@@ -87,13 +87,13 @@ properties:
     items:
       type: object
       properties:
-        sample_name: 
+        sample_name:
           type: string
           description: "Name of the sample"
-        organism: 
+        organism:
           type: string
           description: "Organism"
-        protocol: 
+        protocol:
           type: string
           description: "Must be an ATAC-seq or DNAse-seq sample"
         genome:
diff --git a/docs_jupyter/demo.ipynb b/docs_jupyter/demo.ipynb
index 09b3de8c..6e753779 100644
--- a/docs_jupyter/demo.ipynb
+++ b/docs_jupyter/demo.ipynb
@@ -372,4 +372,4 @@
  },
  "nbformat": 4,
  "nbformat_minor": 2
-}
\ No newline at end of file
+}
diff --git a/eido/_version.py b/eido/_version.py
index 1276d025..0a8da882 100644
--- a/eido/_version.py
+++ b/eido/_version.py
@@ -1,1 +1,1 @@
-__version__ = "0.1.5"
+__version__ = "0.1.6"
diff --git a/eido/argparser.py b/eido/argparser.py
index 440aee36..8fa2a491 100644
--- a/eido/argparser.py
+++ b/eido/argparser.py
@@ -1,5 +1,7 @@
 from logging import CRITICAL, DEBUG, ERROR, INFO, WARN
 
+from peppy.const import SAMPLE_NAME_ATTR
+from peppy import __version__ as peppy_version
 from ubiquerg import VersionInHelpParser
 
 from . import __version__
@@ -13,6 +15,8 @@
     DEBUG,
 ]
 
+version_combined = f"{__version__} (peppy {peppy_version})"
+
 
 def build_argparser():
     banner = "%(prog)s - Interact with PEPs"
@@ -22,7 +26,7 @@ def build_argparser():
         prog=PKG_NAME,
         description=banner,
         epilog=additional_description,
-        version=__version__,
+        version=version_combined,
     )
 
     subparsers = parser.add_subparsers(dest="command")
@@ -43,11 +47,26 @@ def build_argparser():
     sps = {}
     for cmd, desc in SUBPARSER_MSGS.items():
         sps[cmd] = subparsers.add_parser(cmd, description=desc, help=desc)
-        if cmd != FILTERS_CMD:
+        sps[cmd].add_argument(
+            "--st-index",
+            required=False,
+            type=str,
+            help=f"Sample table index to use, samples are identified by '{SAMPLE_NAME_ATTR}' by default.",
+        )
+        if cmd != CONVERT_CMD:
             sps[cmd].add_argument(
                 "pep",
                 metavar="PEP",
                 help="Path to a PEP configuration file in yaml format.",
+                default=None,
+            )
+        else:
+            sps[cmd].add_argument(
+                "pep",
+                metavar="PEP",
+                nargs="?",
+                help="Path to a PEP configuration file in yaml format.",
+                default=None,
             )
 
     sps[VALIDATE_CMD].add_argument(
@@ -109,9 +128,9 @@ def build_argparser():
     sps[CONVERT_CMD].add_argument(
         "-f",
         "--format",
-        required=True,
+        required=False,
         default="yaml",
-        help="Path to a PEP schema file in yaml format.",
+        help="Output format (name of filter; use -l to see available).",
     )
 
     sps[CONVERT_CMD].add_argument(
@@ -132,4 +151,28 @@ def build_argparser():
         help="Provide arguments to the filter function (e.g. arg1=val1 arg2=val2).",
     )
 
-    return parser
+    sps[CONVERT_CMD].add_argument(
+        "-l",
+        "--list",
+        required=False,
+        default=False,
+        action="store_true",
+        help="List available filters.",
+    )
+
+    sps[CONVERT_CMD].add_argument(
+        "-d",
+        "--describe",
+        required=False,
+        default=False,
+        action="store_true",
+        help="Show description for a given filter.",
+    )
+
+    sps[CONVERT_CMD].add_argument(
+        "-p",
+        "--paths",
+        nargs="+",
+        help="Paths to dump conversion result as key=value pairs.",
+    )
+    return parser, sps
diff --git a/eido/cli.py b/eido/cli.py
index 0b22d76d..a1f73592 100644
--- a/eido/cli.py
+++ b/eido/cli.py
@@ -6,7 +6,12 @@
 
 from .argparser import LEVEL_BY_VERBOSITY, build_argparser
 from .const import *
-from .conversion import convert_project, get_available_pep_filters
+from .conversion import (
+    convert_project,
+    get_available_pep_filters,
+    pep_conversion_plugins,
+)
+from .exceptions import EidoFilterError
 from .inspection import inspect_project
 from .validation import validate_config, validate_project, validate_sample
 
@@ -30,8 +35,8 @@ def _parse_filter_args_str(input):
 
 
 def main():
-    """ Primary workflow """
-    parser = build_argparser()
+    """Primary workflow"""
+    parser, sps = build_argparser()
     args, remaining_args = parser.parse_known_args()
 
     if args.command is None:
@@ -54,19 +59,49 @@ def main():
     global _LOGGER
     _LOGGER = init_logger(name=PKG_NAME, **logger_kwargs)
 
-    if args.command == FILTERS_CMD:
+    if args.command == CONVERT_CMD:
         filters = get_available_pep_filters()
-        if len(filters) < 1:
-            _LOGGER.info("No available filters")
+        if args.list:
+            _LOGGER.info("Available filters:")
+            if len(filters) < 1:
+                _LOGGER.info("No available filters")
+            for filter_name in filters:
+                _LOGGER.info(f" - {filter_name}")
+            sys.exit(0)
+        if not "format" in args:
+            _LOGGER.info("The following arguments are required: --format")
+            sps[CONVERT_CMD].print_help(sys.stderr)
+            sys.exit(1)
+        if args.describe:
+            if args.format not in filters:
+                raise EidoFilterError(
+                    f"'{args.format}' filter not found. Available filters: {', '.join(filters)}"
+                )
+            filter_functions_by_name = pep_conversion_plugins()
+            print(filter_functions_by_name[args.format].__doc__)
             sys.exit(0)
-        _LOGGER.info("Available filters:")
-        for filter_name in filters:
-            _LOGGER.info(f" - {filter_name}")
+        if args.pep is None:
+            sps[CONVERT_CMD].print_help(sys.stderr)
+            _LOGGER.info("The following arguments are required: PEP")
+            sys.exit(1)
+        if args.paths:
+            paths = {y[0]: y[1] for y in [x.split("=") for x in args.paths]}
+        else:
+            paths = None
+
+        p = Project(args.pep, sample_table_index=args.st_index)
+        plugin_kwargs = _parse_filter_args_str(args.args)
+
+        # append paths
+        plugin_kwargs["paths"] = paths
+
+        convert_project(p, args.format, plugin_kwargs)
+        _LOGGER.info("Conversion successful")
         sys.exit(0)
 
     _LOGGER.debug(f"Creating a Project object from: {args.pep}")
-    p = Project(args.pep)
     if args.command == VALIDATE_CMD:
+        p = Project(cfg=args.pep, sample_table_index=args.st_index)
         if args.sample_name:
             try:
                 args.sample_name = int(args.sample_name)
@@ -88,13 +123,8 @@ def main():
             )
             validate_project(p, args.schema, args.exclude_case)
         _LOGGER.info("Validation successful")
+
     if args.command == INSPECT_CMD:
+        p = Project(cfg=args.pep, sample_table_index=args.st_index)
         inspect_project(p, args.sample_name, args.attr_limit)
         sys.exit(0)
-
-    if args.command == CONVERT_CMD:
-        p = Project(args.pep)
-        plugin_kwargs = _parse_filter_args_str(args.args)
-        convert_project(p, args.format, plugin_kwargs)
-        _LOGGER.info("Conversion successful")
-        sys.exit(0)
diff --git a/eido/const.py b/eido/const.py
index 43eddaec..e6af5e5c 100644
--- a/eido/const.py
+++ b/eido/const.py
@@ -9,10 +9,9 @@
 CONVERT_CMD = "convert"
 FILTERS_CMD = "filters"
 SUBPARSER_MSGS = {
-    VALIDATE_CMD: "Validate the PEP or its components.",
-    INSPECT_CMD: "Inspect a PEP.",
-    CONVERT_CMD: "Convert a PEP using an available filter",
-    FILTERS_CMD: "List available filters",
+    VALIDATE_CMD: "Validate a PEP or its components",
+    INSPECT_CMD: "Inspect a PEP",
+    CONVERT_CMD: "Convert PEP format using filters",
 }
 PROP_KEY = "properties"
 REQUIRED_FILES_KEY = "required_files"
diff --git a/eido/conversion.py b/eido/conversion.py
index cab1b672..4e4835f5 100644
--- a/eido/conversion.py
+++ b/eido/conversion.py
@@ -1,5 +1,6 @@
 import inspect
 import sys
+import os
 from logging import getLogger
 
 from pkg_resources import iter_entry_points
@@ -39,11 +40,10 @@ def convert_project(prj, target_format, plugin_kwargs=None):
     :param str target_format: the format to convert the Project object to
     :raise EidoFilterError: if the requested filter is not defined
     """
-    run_filter(prj, target_format, plugin_kwargs or dict())
-    sys.exit(0)
+    return run_filter(prj, target_format, plugin_kwargs or dict())
 
 
-def run_filter(prj, filter_name, plugin_kwargs=None):
+def run_filter(prj, filter_name, verbose=True, plugin_kwargs=None):
     """
     Run a selected filter on a peppy.Project object
 
@@ -52,8 +52,21 @@ def run_filter(prj, filter_name, plugin_kwargs=None):
     :param dict plugin_kwargs: kwargs to pass to the plugin function
     :raise EidoFilterError: if the requested filter is not defined
     """
+    # convert to empty dictionary if no plugin_kwargs are passed
+    plugin_kwargs = plugin_kwargs or dict()
+
+    # get necessary objects
     installed_plugins = pep_conversion_plugins()
     installed_plugin_names = list(installed_plugins.keys())
+    paths = plugin_kwargs.get("paths")
+    env = plugin_kwargs.get("env")
+
+    # set environment
+    if env is not None:
+        for var in env:
+            os.environ[var] = env[var]
+
+    # check for valid filter
     if filter_name not in installed_plugin_names:
         raise EidoFilterError(
             f"Requested filter ({filter_name}) not found. "
@@ -61,8 +74,37 @@ def run_filter(prj, filter_name, plugin_kwargs=None):
         )
     _LOGGER.info(f"Running plugin {filter_name}")
     func = installed_plugins[filter_name]
-    plugin_kwargs = plugin_kwargs or dict()
-    func(prj, **plugin_kwargs)
+
+    # run filter
+    conv_result = func(prj, **plugin_kwargs)
+
+    # if paths supplied, write to disk
+    if paths is not None:
+        # map conversion result to the
+        # specified path
+        for result_key in conv_result:
+            result_path = paths.get(result_key)
+            if result_path is None:
+                _LOGGER.warning(
+                    f"Conversion plugin returned key that doesn't exist in specified paths: '{result_key}'."
+                )
+            else:
+                # create path if it doesn't exist
+                if not os.path.exists(result_path):
+                    os.makedirs(os.path.dirname(result_path), exist_ok=True)
+                # write to path
+                with open(result_path, "w") as f:
+                    f.write(conv_result[result_key])
+
+    if verbose:
+        for result_key in conv_result:
+            sys.stdout.write(conv_result[result_key])
+    else:
+        # simply return from the function with
+        # conversion results
+        pass
+
+    return conv_result
 
 
 def get_available_pep_filters():
diff --git a/eido/conversion_plugins.py b/eido/conversion_plugins.py
index 0bfe69b3..5244b253 100644
--- a/eido/conversion_plugins.py
+++ b/eido/conversion_plugins.py
@@ -1,8 +1,8 @@
 """ built-in PEP filters """
-import sys
+from typing import Dict
 
 
-def basic_pep_filter(p, **kwargs):
+def basic_pep_filter(p, **kwargs) -> Dict[str, str]:
     """
     Basic PEP filter, that does not convert the Project object.
 
@@ -10,15 +10,10 @@ def basic_pep_filter(p, **kwargs):
 
     :param peppy.Project p: a Project to run filter on
     """
-    path = kwargs.get("path")
-    if path is not None:
-        with open(path, "w") as text_file:
-            text_file.write(p.__str__)
-    else:
-        print(p)
+    return {"project": str(p)}
 
 
-def yaml_samples_pep_filter(p, **kwargs):
+def yaml_samples_pep_filter(p, **kwargs) -> Dict[str, str]:
     """
     YAML samples PEP filter, that returns only Sample object representations.
 
@@ -26,27 +21,16 @@ def yaml_samples_pep_filter(p, **kwargs):
 
     :param peppy.Project p: a Project to run filter on
     """
-    path = kwargs.get("path")
-    if path is not None:
-        from yaml import dump
-
-        samples_yaml = []
-        for s in p.samples:
-            samples_yaml.append(s.to_dict())
-        with open(path, "w") as outfile:
-            dump(samples_yaml, outfile, default_flow_style=False)
-    else:
-        import re
+    from yaml import dump
 
-        from yaml import safe_dump
+    samples_yaml = []
+    for s in p.samples:
+        samples_yaml.append(s.to_dict())
 
-        for s in p.samples:
-            sys.stdout.write("- ")
-            out = re.sub("\n", "\n  ", safe_dump(s.to_dict(), default_flow_style=False))
-            sys.stdout.write(out + "\n")
+    return {"samples": dump(samples_yaml, default_flow_style=False)}
 
 
-def yaml_pep_filter(p, **kwargs):
+def yaml_pep_filter(p, **kwargs) -> Dict[str, str]:
     """
     YAML PEP filter, that returns Project object representation.
 
@@ -56,17 +40,11 @@ def yaml_pep_filter(p, **kwargs):
     """
     from yaml import dump
 
-    path = kwargs.get("path")
-    if path is not None:
-        data = p.config.to_dict()
-        with open(path, "w") as outfile:
-            dump(data, outfile, default_flow_style=False)
-    else:
-        data = p.config.to_yaml()
-        print(data)
+    data = p.config.to_dict()
+    return {"project": dump(data, default_flow_style=False)}
 
 
-def csv_pep_filter(p, **kwargs):
+def csv_pep_filter(p, **kwargs) -> Dict[str, str]:
     """
     CSV PEP filter, that returns Sample object representations
 
@@ -78,11 +56,40 @@ def csv_pep_filter(p, **kwargs):
     sample_table_path = kwargs.get("sample_table_path")
     subsample_table_path = kwargs.get("subsample_table_path")
     sample_table_repr = p.sample_table.to_csv(path_or_buf=sample_table_path)
+
+    s = ""
     if sample_table_repr is not None:
-        sys.stdout.write(sample_table_repr)
+        s += sample_table_repr
     if p.subsample_table is not None:
         subsample_table_repr = p.subsample_table.to_csv(
             path_or_buf=subsample_table_path
         )
         if subsample_table_repr is not None:
-            sys.stdout.write(subsample_table_repr)
+            s += subsample_table_repr
+
+    return {"samples": s}
+
+
+def processed_pep_filter(p, **kwargs) -> Dict[str, str]:
+    """
+    Processed PEP filter, that returns the converted sample and subsample tables.
+    This filter can return the tables as a table or a document.
+    :param peppy.Project p: a Project to run filter on
+    :param bool samples_as_objects: Flag to write as a table
+    :param bool subsamples_as_objects: Flag to write as a table
+    """
+    # get params
+    samples_as_objects = kwargs.get("samples_as_objects")
+    subsamples_as_objects = kwargs.get("subsamples_as_objects")
+
+    prj_repr = p.config.to_dict()
+
+    return {
+        "project": str(prj_repr),
+        "samples": str(p.samples)
+        if samples_as_objects
+        else str(p.sample_table.to_csv()),
+        "subsamples": str(p.subsamples)
+        if subsamples_as_objects
+        else str(p.subsample_table.to_csv()),
+    }
diff --git a/eido/exceptions.py b/eido/exceptions.py
index 594c35cd..d37ad4c7 100644
--- a/eido/exceptions.py
+++ b/eido/exceptions.py
@@ -6,27 +6,27 @@
 
 
 class EidoException(Exception):
-    """ Base type for custom package errors. """
+    """Base type for custom package errors."""
 
     __metaclass__ = ABCMeta
 
 
 class PathAttrNotFoundError(EidoException):
-    """ Path-like argument does not exist. """
+    """Path-like argument does not exist."""
 
     def __init__(self, key):
         super(PathAttrNotFoundError, self).__init__(key)
 
 
 class EidoSchemaInvalidError(EidoException):
-    """ Schema does not comply to eido-specific requirements. """
+    """Schema does not comply to eido-specific requirements."""
 
     def __init__(self, key):
         super(EidoSchemaInvalidError, self).__init__(key)
 
 
 class EidoFilterError(EidoException):
-    """ Issue with the PEP filter. """
+    """Issue with the PEP filter."""
 
     def __init__(self, key):
         super(EidoFilterError, self).__init__(key)
diff --git a/eido/schema.py b/eido/schema.py
index 73e50aa6..7f3794a1 100644
--- a/eido/schema.py
+++ b/eido/schema.py
@@ -24,19 +24,26 @@ def preprocess_schema(schema_dict):
     if "config" in schema_dict[PROP_KEY]:
         schema_dict[PROP_KEY]["_config"] = schema_dict[PROP_KEY]["config"]
         del schema_dict[PROP_KEY]["config"]
+    else:
+        _LOGGER.debug("No config section found in schema")
     if "samples" in schema_dict[PROP_KEY]:
         schema_dict[PROP_KEY]["_samples"] = schema_dict[PROP_KEY]["samples"]
         del schema_dict[PROP_KEY]["samples"]
-        schema_dict["required"][schema_dict["required"].index("samples")] = "_samples"
-    if (
-        "items" in schema_dict[PROP_KEY]["_samples"]
-        and PROP_KEY in schema_dict[PROP_KEY]["_samples"]["items"]
-    ):
-        s_props = schema_dict[PROP_KEY]["_samples"]["items"][PROP_KEY]
-        for prop, val in s_props.items():
-            if "type" in val and val["type"] in ["string", "number", "boolean"]:
-                s_props[prop] = {}
-                s_props[prop]["anyOf"] = [val, {"type": "array", "items": val}]
+        if "required" in schema_dict:
+            schema_dict["required"][
+                schema_dict["required"].index("samples")
+            ] = "_samples"
+        if (
+            "items" in schema_dict[PROP_KEY]["_samples"]
+            and PROP_KEY in schema_dict[PROP_KEY]["_samples"]["items"]
+        ):
+            s_props = schema_dict[PROP_KEY]["_samples"]["items"][PROP_KEY]
+            for prop, val in s_props.items():
+                if "type" in val and val["type"] in ["string", "number", "boolean"]:
+                    s_props[prop] = {}
+                    s_props[prop]["anyOf"] = [val, {"type": "array", "items": val}]
+    else:
+        _LOGGER.debug("No samples section found in schema")
     _LOGGER.debug(f"schema processed: {schema_dict}")
     return schema_dict
 
diff --git a/eido/validation.py b/eido/validation.py
index 0a23b56f..744eac1e 100644
--- a/eido/validation.py
+++ b/eido/validation.py
@@ -7,7 +7,15 @@
 from pandas.core.common import flatten
 from ubiquerg import size
 
-from .const import *
+from .const import (
+    ALL_INPUTS_KEY,
+    FILES_KEY,
+    INPUT_FILE_SIZE_KEY,
+    MISSING_KEY,
+    PROP_KEY,
+    REQUIRED_FILES_KEY,
+    REQUIRED_INPUTS_KEY,
+)
 from .schema import preprocess_schema, read_schema
 
 _LOGGER = getLogger(__name__)
@@ -60,7 +68,9 @@ def _validate_sample_object(sample, schemas, exclude_case=False):
         schema_dict = preprocess_schema(schema_dict)
         sample_schema_dict = schema_dict[PROP_KEY]["_samples"]["items"]
         _validate_object(sample, sample_schema_dict, exclude_case)
-        _LOGGER.debug("'{}' sample validation successful".format(sample.sample_name))
+        _LOGGER.debug(
+            f"{getattr(sample, 'sample_name', '')} sample validation successful"
+        )
 
 
 def validate_sample(project, sample_name, schema, exclude_case=False):
@@ -120,7 +130,7 @@ def validate_inputs(sample, schema, exclude_case=False):
     perform actual Sample object validation with jsonschema.
 
     :param peppy.Sample sample: sample to investigate
-    :param list[dict] schema: schema dict to validate against
+    :param list[dict] | str schema: schema dict to validate against or a path to one
     :return dict: dictionary with validation data, i.e missing,
         required_inputs, all_inputs, input_file_size
     :param bool exclude_case: whether to exclude validated objects
@@ -148,6 +158,9 @@ def _get_attr_values(obj, attrlist):
         # Strings contained here are appended later so shouldn't be null.
         return list(flatten([getattr(obj, attr, "") for attr in attrlist]))
 
+    if isinstance(schema, str):
+        schema = read_schema(schema)
+
     # validate attrs existence first
     _validate_sample_object(schemas=schema, sample=sample, exclude_case=exclude_case)
 
@@ -165,7 +178,7 @@ def _get_attr_values(obj, attrlist):
     with cw(record=True) as w:
         input_file_size = sum(
             [size(f, size_str=False) or 0.0 for f in all_inputs if f != ""]
-        ) / (1024 ** 3)
+        ) / (1024**3)
         if w:
             _LOGGER.warning(
                 f"{len(w)} input files missing, job input size was "
diff --git a/mkdocs.yml b/mkdocs.yml
index 269ed3eb..dcb5eee8 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -3,6 +3,7 @@ site_logo: img/eido_dark.svg
 site_url: http://eido.databio.org/
 repo_url: http://github.com/pepkit/eido
 pypi_name: eido
+paper_link: https://doi.org/10.1101/2020.10.08.331322
 
 nav:
   - Getting Started:
@@ -11,7 +12,7 @@ nav:
   - How-to Guides:
     - "Tutorial: eido in Python": demo.md
     - "Tutorial: eido in a shell": cli.md
-    - Eido filters: filters.md
+    - Using eido filters: filters.md
     - Writing a custom filter: writing-a-filter.md
     - Writing a schema: writing-a-schema.md
   - Reference:
diff --git a/requirements/requirements-all.txt b/requirements/requirements-all.txt
index 2fdc3f7d..882b8d2d 100644
--- a/requirements/requirements-all.txt
+++ b/requirements/requirements-all.txt
@@ -1,5 +1,5 @@
 jsonschema>=3.0.1
 logmuse>=0.2.5
 pandas
-peppy>=0.31.1
+peppy>=0.32.0
 ubiquerg>=0.5.2
diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt
index e69de29b..10fe0d16 100644
--- a/requirements/requirements-dev.txt
+++ b/requirements/requirements-dev.txt
@@ -0,0 +1,2 @@
+-e git+https://github.com/pepkit/attmap@dev#egg=attmap
+-e git+https://github.com/pepkit/peppy@dev#egg=peppy
diff --git a/requirements/requirements-doc.txt b/requirements/requirements-doc.txt
index c0ff3e03..22832ea4 100644
--- a/requirements/requirements-doc.txt
+++ b/requirements/requirements-doc.txt
@@ -1,5 +1,5 @@
-mkdocs>=1.0
+eido
+https://github.com/databio/mkdocs-databio/archive/master.zip
 markdown-include
+mkdocs>=1.0
 pydoc-markdown
-https://github.com/databio/mkdocs-databio/archive/master.zip
-eido
diff --git a/setup.py b/setup.py
index 0101e1e8..929b295f 100644
--- a/setup.py
+++ b/setup.py
@@ -17,12 +17,8 @@
         # DEPENDENCIES.append(line.split("=")[0].rstrip("<>"))
         DEPENDENCIES.append(line)
 
-# 2to3
-if sys.version_info >= (3,):
-    extra["use_2to3"] = True
 extra["install_requires"] = DEPENDENCIES
 
-
 # Additional files to include with package
 def get_static(name, condition=None):
     static = [
@@ -45,13 +41,8 @@ def get_static(name, condition=None):
 with open("eido/_version.py", "r") as versionfile:
     version = versionfile.readline().split()[-1].strip("\"'\n")
 
-# Handle the pypi README formatting.
-try:
-    import pypandoc
-
-    long_description = pypandoc.convert_file("README.md", "rst")
-except (IOError, ImportError, OSError):
-    long_description = open("README.md").read()
+with open("README.md") as f:
+    long_description = f.read()
 
 setup(
     name="eido",
@@ -71,7 +62,7 @@ def get_static(name, condition=None):
     ],
     keywords="project, metadata, bioinformatics, sequencing, ngs, workflow",
     url="https://github.com/pepkit/eido/",
-    author=u"Michal Stolarczyk, Nathan Sheffield",
+    author="Michal Stolarczyk, Nathan Sheffield",
     license="BSD2",
     entry_points={
         "console_scripts": ["eido = eido.__main__:main"],
diff --git a/webeido/.gitignore b/webeido/.gitignore
new file mode 100644
index 00000000..3f549faf
--- /dev/null
+++ b/webeido/.gitignore
@@ -0,0 +1,1 @@
+uploads
diff --git a/webeido/Dockerfile b/webeido/Dockerfile
new file mode 100644
index 00000000..195f18aa
--- /dev/null
+++ b/webeido/Dockerfile
@@ -0,0 +1,8 @@
+FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8-slim
+LABEL authors="Nathan Sheffield"
+
+COPY webeido /app/
+COPY requirements/requirements-all.txt requirements.txt
+RUN pip install -r requirements.txt
+RUN pip install https://github.com/pepkit/peppy/archive/dev.zip
+RUN pip install https://github.com/pepkit/eido/archive/dev.zip
diff --git a/webeido/LICENSE.txt b/webeido/LICENSE.txt
new file mode 100644
index 00000000..926929bf
--- /dev/null
+++ b/webeido/LICENSE.txt
@@ -0,0 +1,9 @@
+Copyright 2021 Nathan Sheffield
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/webeido/MANIFEST.in b/webeido/MANIFEST.in
new file mode 100644
index 00000000..fa282e6a
--- /dev/null
+++ b/webeido/MANIFEST.in
@@ -0,0 +1,5 @@
+include requirements/*
+include README.md
+include LICENSE.txt
+include Dockerfile
+recursive-include webeido *
\ No newline at end of file
diff --git a/webeido/README.md b/webeido/README.md
index 4dabe27a..d8388fe8 100644
--- a/webeido/README.md
+++ b/webeido/README.md
@@ -5,7 +5,32 @@ This is a web interface to eido built using FastAPI
 Run with:
 
 ```
-uvicorn webeido:app --reload
+
+uvicorn main:app --reload
 ```
 
 Then, drop all files belonging to a PEP (yaml + csvs) and upload them. webeido will validate against a list of schemas and show you what tools your PEP will work on.
+
+
+
+
+
+You can install, but it's just so much nicer to run it with `uvicorn --reload` because then changes render in real time.
+
+
+```
+docker build --no-cache -t webeido .
+```
+
+
+
+To run in a container:
+
+```
+docker run --rm -p 8000:80 --name webeido \
+  --volume upload:/upload \
+  webeido uvicorn main:app --reload
+```
+```
+docker run --rm -p 8000:80 --name webeido webeido
+```
diff --git a/webeido/requirements/requirements-all.txt b/webeido/requirements/requirements-all.txt
new file mode 100644
index 00000000..a064a75a
--- /dev/null
+++ b/webeido/requirements/requirements-all.txt
@@ -0,0 +1,9 @@
+aiofiles
+eido
+fastapi
+jinja2
+logmuse>=0.2
+peppy
+python-multipart
+uvicorn>=0.7.1
+ubiquerg>=0.6.1
diff --git a/webeido/schemas.yaml b/webeido/schemas.yaml
new file mode 100644
index 00000000..14948cf3
--- /dev/null
+++ b/webeido/schemas.yaml
@@ -0,0 +1,20 @@
+pipelines/ProseqPEP:
+  name: "PEPPRO"
+  docs: "http://peppro.databio.org"
+  schema: "http://schema.databio.org/pipelines/ProseqPEP.yaml"
+pep/2.0.0:
+  name: "PEP v2.0.0"
+  docs: "http://pep.databio.org"
+  schema: "http://schema.databio.org/pep/2.0.0.yaml"
+pipelines/pepatac:
+  name: "PEPATAC"
+  docs: "http://pepatac.databio.org"
+  schema: "http://schema.databio.org/pipelines/pepatac.yaml"
+pipelines/bedmaker:
+  name: "bedmaker"
+  docs: "http://bedbase.org"
+  schema: "http://schema.databio.org/pipelines/bedmaker.yaml"
+bulker/manifest:
+  name: "bulker"
+  docs: "http://bulker.io"
+  schema: "http://schema.databio.org/bulker/manifest.yaml"
diff --git a/webeido/setup.py b/webeido/setup.py
new file mode 100644
index 00000000..5bb0de38
--- /dev/null
+++ b/webeido/setup.py
@@ -0,0 +1,59 @@
+#! /usr/bin/env python
+
+from setuptools import setup
+import sys
+
+PACKAGE = "webeido"
+
+# Additional keyword arguments for setup().
+extra = {}
+
+# Ordinary dependencies
+DEPENDENCIES = []
+with open("requirements/requirements-all.txt", "r") as reqs_file:
+    for line in reqs_file:
+        print(line)
+        if not line.strip():
+            continue
+        DEPENDENCIES.append(line)
+
+# 2to3
+if sys.version_info >= (3,):
+    extra["use_2to3"] = True
+extra["install_requires"] = DEPENDENCIES
+
+
+with open("{}/_version.py".format(PACKAGE), "r") as versionfile:
+    version = versionfile.readline().split()[-1].strip("\"'\n")
+
+with open("README.md") as f:
+    long_description = f.read()
+
+setup(
+    name=PACKAGE,
+    packages=[PACKAGE],
+    version=version,
+    description="Web-based PEP validator using eido",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    classifiers=[
+        "Development Status :: 4 - Beta",
+        "License :: OSI Approved :: BSD License",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Topic :: Scientific/Engineering :: Bio-Informatics",
+    ],
+    keywords="bioinformatics, sequencing, ngs, metadata, reproducibility",
+    url="https://eido.databio.org/",
+    author="Nathan Sheffield",
+    license="BSD2",
+    entry_points={
+        "console_scripts": [
+            "{p} = {p}.__main__:main".format(p=PACKAGE),
+        ],
+    },
+    include_package_data=True,
+    **extra
+)
diff --git a/webeido/templates/index.html b/webeido/templates/index.html
index b97d0568..406bbb74 100644
--- a/webeido/templates/index.html
+++ b/webeido/templates/index.html
@@ -2,98 +2,90 @@
 {% block title %}Index{% endblock %}
 {% block head %}
     {{ super() }}
-    <script>
-        $(document).ready(function(){
-          $('[data-toggle="tooltip"]').tooltip();
-        });
-    </script>
-    <script>
-        window.onload = function() {
-            var welcome_txt = "</i>. Here we provide a web interface and a RESTful API to access genome assets for " +
-                "popular reference genome assemblies. This server is running " +
-                "<a href='https://github.com/databio/refgenieserver'><i>refgenieserver</i></a>. " +
-                "You may use the <a href='http://refgenie.databio.org'>refgenie CLI</a> to automate downloading and " +
-                "organizing these assets using <code>refgenie pull ...</code> from the command line to retrieve " +
-                "archived genome assets."
-            what();
-            function what(){
-                try {
-                    var current_url = window.location.hostname
-                } catch(err) {
-                    var current_url = "refgenomes.databio.org"
-                }
-                document.getElementById("welcome").innerHTML = "Welcome to <i> " + current_url + welcome_txt;
-            };
-        }
-    </script>
 {% endblock %}
 {% block content %}
-{% macro safe_show_asset_attr(x) %}
-    {% if x is none or not x|length %}
-        NA
-    {% else %}
-        {{ x }}
-    {% endif %}
-{% endmacro %}
+<body>
 <div class="container">
     <div class="row">
         <div class="col-12" id="txt">
-            <p id="welcome" class="text-justify"></p>
-            <p>
-                <b>This is a list of genomes hosted by this server:</b>
-                {% for genome in genomes.keys() | sort %}
-                    <a href="#{{ genome }}">{{ genome }}</a>{{ "," if not loop.last }}
-                {% endfor %}
+            <p id="welcome" class="text-justify">
+                Welcome to webeido, a validation server for sample metadata. You POST your sample metadata here and we will validate it, letting you know which schemas you satisfy.
             </p>
         </div>
     </div>
 </div>
 <hr class="featurette-divider">
 <div class="container">
-    {% for genome_name, values in genomes.items() | sort %}
-        <div class="row align-items-center">
-            <div class="col-12" id="genome-asset">
-                <h4 id="{{ genome_name }}">Reference genome: <b>{{ genome_name }}</b></h4>
-                <p>Description: <i>{{ genomes[genome_name].genome_description }}</i></p>
-                <p>Checksum: <code>{{ genomes[genome_name].genome_digest }}</code></p>
-                <table class="table table-sm">
-                    <thead class="thead-light">
-                        <tr>
-                            <th class="actions" style="">download</a></th>
-                            <th class="asset_name" scope="col">asset name:tag</th>
-                            <th class="asset_desc" scope="col">asset description</th>
-                            <th class="size" scope="col">asset/archive size</th>
-                            <th class="checksum" scope="col">archive digest</th>
-                        </tr>
-                    </thead>
-                    <tbody>
-                        {% for asset_name, asset_data in values.assets.items() | sort %}
-                            {% for tag_name, tag_data in asset_data.tags.items() | sort %}
-                                {% if tag_name != "default_tag" %}
-                                    <tr>
-                                        <td>
-                                            <a href="/v2/asset/{{ genome_name }}/{{ asset_name }}/archive?tag={{ tag_name }}" class="btn btn-outline-dark btn-sm mr-0" role="button" data-toggle="tooltip" title="Archive">
-                                                <i class="fa fa-download" aria-hidden="true"></i>
-                                            </a>
-                                            <a href="/v2/asset/{{ genome_name }}/{{ asset_name }}?tag={{ tag_name }}" class="btn btn-outline-dark btn-sm ml-0" role="button" data-toggle="tooltip" title="Attributes">
-                                                <i class="fa fa-tags" aria-hidden="true"></i>
-                                            </a>
-                                            <a href="/v2/asset/{{ genome_name }}/{{ asset_name }}/recipe?tag={{ tag_name }}" class="btn btn-outline-dark btn-sm ml-0" role="button" data-toggle="tooltip" title="Build recipe">
-                                                <i class="fa fa-file-code-o" aria-hidden="true"></i>
-                                            </a>
-                                        </td>
-                                        <td class="asset_name"><a href="/v2/asset/{{ genome_name }}/{{ asset_name }}/splash?tag={{ tag_name }}" data-toggle="tooltip" title="Asset splash page"><b>{{ asset_name }}</b>:{{ tag_name }}</a></td>
-                                        <td class="asset_desc">{{ safe_show_asset_attr(asset_data["asset_description"]) }}</td>
-                                        <td class="size">{{ safe_show_asset_attr(tag_data["asset_size"]) }}/{{ safe_show_asset_attr(tag_data["archive_size"]) }}</td>
-                                        <td class="checksum"><code>{{ safe_show_asset_attr(tag_data["archive_digest"]) }}</code></td>
-                                    </tr>
-                                {% endif %}
-                            {% endfor %}
-                        {% endfor %}
-                    </tbody>
-                </table>
-            </div>
+    <div>
+    <h1>File Upload</h1>
+        Drag and drop all parts of your PEP here. This includes metadata only, which is the config YAML file, any sample or subsample table CSV files, etc.
+        <div style="margin: 25px; padding:25px; border: 1px solid gray">
+            <form action="/validate/" enctype="multipart/form-data" method="post">
+            <input name="files" type="file" multiple>
+            <input type="submit" value="Validate">
+            </form>
         </div>
-    {% endfor %}
+    </div>
 </div>
-{% endblock %}
\ No newline at end of file
+<hr class="featurette-divider">
+<div class="container">
+    <div>
+    <h1>POST your data programatically</h1>
+        The preferred way to use webeido is to POST your PEP to the /validate endpoint. Here's how you do that. Just POST your files following this format. Files can be named whatever you want.
+
+        <div style="margin: 25px; padding:25px; border: 1px solid gray">
+        <code><pre> 
+            { 
+                files: [
+                    "project_config.yaml",
+                    "sample_table.csv"
+                ]
+            }</pre></code>
+        </div>
+
+        Return looks like: 
+<code><pre> 
+{
+    "name": "uploads/project_config.yaml",
+    "filenames": [
+        "project_config.yaml",
+        "sample_table.csv"
+    ],
+    "validations": [
+        {
+            "name": "Generic PEP",
+            "schema": "http://schema.databio.org/pep/2.0.0.yaml",
+            "result": "None"
+        },
+        {
+            "name": "PEPPRO",
+            "schema": "http://schema.databio.org/pipelines/ProseqPEP.yaml",
+            "result": "'read1' is a required property"
+        },
+        {
+            "name": "PEPATAC",
+            "schema": "http://schema.databio.org/pipelines/pepatac.yaml",
+            "result": "'read1' is a required property"
+        },
+        {
+            "name": "bedmaker",
+            "schema": "http://schema.databio.org/pipelines/bedmaker.yaml",
+            "result": "'input_file_path' is a required property"
+        },
+        {
+            "name": "refgenie",
+            "schema": "http://schema.databio.org/refgenie/refgenie_build.yaml",
+            "result": "'asset' is a required property"
+        },
+        {
+            "name": "bulker",
+            "schema": "http://schema.databio.org/bulker/manifest.yaml",
+            "result": "None"
+        }
+    ]
+}
+</pre></code>
+    </div>
+</div>
+</body>
+{% endblock %}
diff --git a/webeido/webeido.py b/webeido/webeido.py
index 24cc53e4..486055bf 100644
--- a/webeido/webeido.py
+++ b/webeido/webeido.py
@@ -1,57 +1,128 @@
-from typing import List
+import eido
+import jinja2
+import os
+import shutil
+import yaml
 
 from fastapi import FastAPI, File, UploadFile
+from fastapi.middleware.cors import CORSMiddleware
+from peppy import Project
+from peppy import __version__ as peppy_version
 from starlette.requests import Request
 from starlette.responses import HTMLResponse
+from starlette.responses import JSONResponse
 from starlette.staticfiles import StaticFiles
 from starlette.templating import Jinja2Templates
+from typing import List
+from yacman import load_yaml
 
 app = FastAPI()
 
-import os
-import shutil
-
-import jinja2
-import yaml
-from peppy import Project
-
-import eido
+origins = ["*"]
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=origins,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
 
 TEMPLATES_PATH = "templates"
 templates = Jinja2Templates(directory=TEMPLATES_PATH)
 je = jinja2.Environment(loader=jinja2.FileSystemLoader(TEMPLATES_PATH))
 
-
 # add static for images
 app.mount("/" + "static", StaticFiles(directory="static"), name="static")
 
-
-@app.get("/hello")
-async def root():
-    return {"message": "Hello World"}
-
-
-# Removed links from template because url_for gave errors:
-
-
-schemas_to_test = {
-    "Generic PEP": "http://schema.databio.org/pep/2.0.0.yaml",
-    "PEPPRO": "http://schema.databio.org/pipelines/ProseqPEP.yaml",
-    "PEPATAC": "http://schema.databio.org/pipelines/pepatac.yaml",
-    "bedmaker": "http://schema.databio.org/pipelines/bedmaker.yaml",
-    "refgenie": "http://schema.databio.org/refgenie/refgenie_build.yaml",
-    "bulker": "http://schema.databio.org/bulker/manifest.yaml",
-}
+print("Schemas list")
+print(load_yaml("schemas.yaml"))
+
+schemas_to_test = load_yaml("schemas.yaml")
+
+
+@app.get("/status")
+async def status(request: Request):
+    return JSONResponse({"status": "OK"})
+
+
+@app.get("/schemas")
+async def status(request: Request):
+    return JSONResponse(schemas_to_test)
+
+
+# This endpoint validates for one specific schema
+# when the schema is known to the server by registry path
+@app.post("/validate/{namespace}/{schemaid}")
+async def validate_specific_schema(
+    namespace, schemaid, request: Request, files: List[UploadFile] = File(...)
+):
+    # print(schemas_to_test)
+    registry_path = "/".join([namespace, schemaid])
+    print(schemas_to_test[registry_path])
+    return await validate_pep(
+        request, files, {registry_path: schemas_to_test[registry_path]}
+    )
+    # return validate_pep(request, files, schemas_to_test[schemaid])
+
+
+# Provide an external schema
+@app.post("/validate_one")
+async def validate_one(
+    request: Request,
+    files: List[UploadFile] = File(...),
+    schemas_to_test=schemas_to_test,
+):
+    # TODO implement this
+    return False
+
+
+def vwrap(p, schema):
+    x = None
+    try:
+        eido.validate_project(project=p, schema=schema, exclude_case=True)
+    except Exception as e:
+        x = str(e)
+        print(x)
+    return x
+
+
+# Provide a registry path
+@app.get("/validate_fromhub/{namespace}/{project}")
+async def validate_fromhub(
+    namespace: str,
+    pep_id: str,
+):
+    proj = peppy.Project(PEP_STORES[namespace][pep_id])
+    vals = {}
+    for schema_id, schema_data in schemas_to_test.items():
+        vals["validations"].append(
+            {
+                "id": schema_id,
+                "name": schema_data["name"],
+                "docs": schema_data["docs"],
+                "schema": schema_data["schema"],
+                "result": vwrap(p, schema_data["schema"]),
+            }
+        )
+    return JSONResponse(content=vals)
 
 
-@app.post("/validate/")
-async def validate_pep(request: Request, files: List[UploadFile] = File(...)):
+@app.post("/validate")
+async def validate_pep(
+    request: Request,
+    files: List[UploadFile] = File(...),
+    schemas_to_test=schemas_to_test,
+):
     ufiles = []
     upload_folder = "uploads"
     for file in files:
-        print(file)
+        print(f"File: '{file}'")
         file_object = file.file
-        uploaded = open(os.path.join(upload_folder, file.filename), "wb+")
+        full_path = os.path.join(upload_folder, file.filename)
+        # if not os.path.isfile(full_path):
+        #     print(f"failed isfile test: {full_path}")
+        #     return JSONResponse(content={ "error": "No files provided."})
+        uploaded = open(full_path, "wb+")
         shutil.copyfileobj(file_object, uploaded)
         uploaded.close()
         print(uploaded.name)
@@ -64,36 +135,26 @@ async def validate_pep(request: Request, files: List[UploadFile] = File(...)):
     p = Project(pconf)
     print(p)
 
-    def vwrap(p, schema):
-        x = None
-        try:
-            eido.validate_project(project=p, schema=schema, exclude_case=True)
-        except Exception as e:
-            x = e
-            print(x)
-        return str(x)
-
     vals = {
         "name": pconf,
         "filenames": [file.filename for file in files],
-        "request": request,
+        "peppy_version": peppy_version,
         "validations": [],
     }
-    for name, schema in schemas_to_test.items():
+    for schema_id, schema_data in schemas_to_test.items():
         vals["validations"].append(
-            {"name": name, "schema": schema, "result": vwrap(p, schema)}
+            {
+                "id": schema_id,
+                "name": schema_data["name"],
+                "docs": schema_data["docs"],
+                "schema": schema_data["schema"],
+                "result": vwrap(p, schema_data["schema"]),
+            }
         )
-    return HTMLResponse(je.get_template("validation_results.html").render(**vals))
+    return JSONResponse(content=vals)
+    # return HTMLResponse(je.get_template("validation_results.html").render(**vals))
 
 
 @app.get("/")
 async def main():
-    content = """
-<body>
-<form action="/validate/" enctype="multipart/form-data" method="post">
-<input name="files" type="file" multiple>
-<input type="submit">
-</form>
-</body>
-    """
-    return HTMLResponse(content=content)
+    return HTMLResponse(je.get_template("index.html").render())
diff --git a/webeido/webeido/__init__.py b/webeido/webeido/__init__.py
new file mode 100644
index 00000000..56c57580
--- /dev/null
+++ b/webeido/webeido/__init__.py
@@ -0,0 +1,70 @@
+import logmuse
+
+from ubiquerg import VersionInHelpParser
+from _version import __version__ as webeido_version
+
+PKG_NAME = "webeido"
+
+logmuse.init_logger("webeido")
+
+all = ["build_parser"]
+
+
+def build_parser():
+    """
+    Building argument parser
+
+    :return argparse.ArgumentParser
+    """
+    banner = "%(prog)s - Web validator for PEPs"
+    additional_description = (
+        "For subcommand-specific options, type: '%(prog)s <subcommand> -h'"
+    )
+    additional_description += "\nhttps://github.com/pepkit/eido"
+
+    parser = VersionInHelpParser(
+        prog=PKG_NAME, description=banner, epilog=additional_description
+    )
+
+    parser.add_argument(
+        "-V",
+        "--version",
+        action="version",
+        version="%(prog)s {v}".format(v=webeido_version),
+    )
+
+    msg_by_cmd = {"serve": "run the server"}
+
+    subparsers = parser.add_subparsers(dest="command")
+
+    def add_subparser(cmd, description):
+        return subparsers.add_parser(cmd, description=description, help=description)
+
+    sps = {}
+    # add arguments that are common across subparsers
+    for cmd, desc in msg_by_cmd.items():
+        sps[cmd] = add_subparser(cmd, desc)
+        sps[cmd].add_argument(
+            "-c",
+            "--config",
+            required=False,
+            dest="config",
+            help="Path to the config file (YAML).",
+        )
+        sps[cmd].add_argument(
+            "-d",
+            "--dbg",
+            action="store_true",
+            dest="debug",
+            help="Set logger verbosity to debug",
+        )
+    # add subparser-specific arguments
+    sps["serve"].add_argument(
+        "-p",
+        "--port",
+        dest="port",
+        type=int,
+        help="The port the webserver should be run on.",
+        default=8000,
+    )
+    return parser
diff --git a/webeido/webeido/__main__.py b/webeido/webeido/__main__.py
new file mode 100644
index 00000000..2e4396a4
--- /dev/null
+++ b/webeido/webeido/__main__.py
@@ -0,0 +1,9 @@
+import sys
+from .main import main
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main())
+    except KeyboardInterrupt:
+        print("Program canceled by user")
+        sys.exit(1)
diff --git a/webeido/webeido/_version.py b/webeido/webeido/_version.py
new file mode 100644
index 00000000..f102a9ca
--- /dev/null
+++ b/webeido/webeido/_version.py
@@ -0,0 +1,1 @@
+__version__ = "0.0.1"
diff --git a/webeido/webeido/main.py b/webeido/webeido/main.py
new file mode 100644
index 00000000..3375e021
--- /dev/null
+++ b/webeido/webeido/main.py
@@ -0,0 +1,148 @@
+import eido
+import jinja2
+import logmuse
+import logging
+import os
+import shutil
+import yaml
+import uvicorn
+
+from fastapi import FastAPI
+from fastapi import FastAPI, File, UploadFile
+from platform import python_version
+from starlette.responses import HTMLResponse
+from starlette.requests import Request
+from starlette.templating import Jinja2Templates
+from starlette.staticfiles import StaticFiles
+from typing import List
+from peppy import Project
+
+from _version import __version__ as webeido_version
+from __init__ import build_parser
+from eido._version import __version__ as eido_version
+
+ALL_VERSIONS = {
+    "webeido_version": webeido_version,
+    "eido_version": eido_version,
+    "python_version": python_version(),
+}
+
+TEMPLATES_PATH = "templates"
+templates = Jinja2Templates(directory=TEMPLATES_PATH)
+je = jinja2.Environment(loader=jinja2.FileSystemLoader(TEMPLATES_PATH))
+
+_LOGGER = logging.getLogger(__name__)
+_LOGGER.info("Welcome to the webeido app")
+app = FastAPI()
+
+# add static for images
+app.mount("/static", StaticFiles(directory="static"), name="static")
+
+
+@app.get("/hello")
+async def hello():
+    return {"message": "Hello World"}
+
+
+@app.get("/")
+async def root(request: Request):
+    return templates.TemplateResponse(
+        "input.html", dict({"request": request}, **ALL_VERSIONS)
+    )
+
+
+@app.get("/index")
+async def index(request: Request):
+    """
+    Returns a landing page HTML with the server resources ready do download. No inputs required.
+    """
+    templ_vars = {"request": request, "openapi_version": app.openapi()["openapi"]}
+    _LOGGER.debug("merged vars: {}".format(dict(templ_vars, **ALL_VERSIONS)))
+    return templates.TemplateResponse("index.html", dict(templ_vars, **ALL_VERSIONS))
+
+
+# Removed links from template because url_for gave errors:
+
+schemas_to_test = {
+    "Generic PEP": "http://schema.databio.org/pep/2.0.0.yaml",
+    "PEPPRO": "http://schema.databio.org/pipelines/ProseqPEP.yaml",
+    "PEPATAC": "http://schema.databio.org/pipelines/pepatac.yaml",
+    "bedmaker": "http://schema.databio.org/pipelines/bedmaker.yaml",
+    "refgenie": "http://schema.databio.org/refgenie/refgenie_build.yaml",
+    "bulker": "http://schema.databio.org/bulker/manifest.yaml",
+}
+
+
+@app.post("/upload/")
+async def upload(request: Request, file: UploadFile = File(...)):
+    upload_folder = "uploads"
+    print(file)
+    file_object = file.file
+    uploaded = open(os.path.join(upload_folder, file.filename), "wb+")
+    shutil.copyfileobj(file_object, uploaded)
+    uploaded.close()
+    print(uploaded.name)
+    f, ext = os.path.splitext(file.filename)
+    print(ext)
+    if ext == ".yaml" or ext == ".yml":
+        pconf = uploaded.name
+        print("Got yaml:", pconf)
+
+
+@app.post("/validate/")
+async def validate_pep(request: Request, files: List[UploadFile] = File(...)):
+    ufiles = []
+    upload_folder = "uploads"
+    for file in files:
+        print(file)
+        file_object = file.file
+        uploaded = open(os.path.join(upload_folder, file.filename), "wb+")
+        shutil.copyfileobj(file_object, uploaded)
+        uploaded.close()
+        print(uploaded.name)
+        f, ext = os.path.splitext(file.filename)
+        print(ext)
+        if ext == ".yaml" or ext == ".yml":
+            pconf = uploaded.name
+            print("Got yaml:", pconf)
+    print(pconf)
+    p = Project(pconf)
+    print(p)
+
+    def vwrap(p, schema):
+        x = None
+        try:
+            eido.validate_project(project=p, schema=schema, exclude_case=True)
+        except Exception as e:
+            x = e
+            print(x)
+        return str(x)
+
+    vals = {
+        "name": pconf,
+        "filenames": [file.filename for file in files],
+        "request": request,
+        "validations": [],
+    }
+    for name, schema in schemas_to_test.items():
+        vals["validations"].append(
+            {"name": name, "schema": schema, "result": vwrap(p, schema)}
+        )
+    return HTMLResponse(je.get_template("validation_results.html").render(**vals))
+
+
+def main():
+    global _LOGGER
+    parser = build_parser()
+    parser = logmuse.add_logging_options(parser)
+    args = parser.parse_args()
+    if not args.command:
+        parser.print_help()
+        print("No subcommand given")
+        sys.exit(1)
+
+    _LOGGER = logmuse.logger_via_cli(args, make_root=True)
+    _LOGGER.info("Welcome to the webeido app")
+    args.port
+    _LOGGER.info("Running on port {}".format(args.port))
+    uvicorn.run(app, host="0.0.0.0", port=args.port)
diff --git a/webeido/static/eido.svg b/webeido/webeido/static/eido.svg
similarity index 100%
rename from webeido/static/eido.svg
rename to webeido/webeido/static/eido.svg
diff --git a/webeido/webeido/static/eido_light.svg b/webeido/webeido/static/eido_light.svg
new file mode 100644
index 00000000..1c148ecf
--- /dev/null
+++ b/webeido/webeido/static/eido_light.svg
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="24.766375"
+   width="66.525208"
+   version="1.1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 66.525213 24.766375"
+   id="svg6"
+   sodipodi:docname="eido.svg"
+   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+   style="fill:#000000">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1139"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="5.9962655"
+     inkscape:cx="21.546709"
+     inkscape:cy="-1.8650012"
+     inkscape:current-layer="svg6"
+     fit-margin-top="2"
+     fit-margin-left="2"
+     fit-margin-right="2"
+     fit-margin-bottom="2"
+     inkscape:snap-global="false"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <g
+     id="g1336"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.66666698px;line-height:1;font-family:Arial;-inkscape-font-specification:Arial;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.06699991;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+     aria-label="eido"
+     transform="matrix(1.5907479,0,0,1.5907479,-74.278792,-41.821679)">
+    <path
+       id="path1328"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ffffff"
+       d="m 66.954402,32.325749 q 0.789333,0 1.397333,0.373333 0.608,0.362667 0.938667,1.013333 0.341333,0.650667 0.341333,1.482667 0,0.170667 -0.106666,0.277334 -0.106667,0.096 -0.277334,0.096 h -4.309333 q 0.08533,0.874666 0.682667,1.418666 0.597333,0.544 1.493333,0.544 0.384,0 0.768,-0.128 0.394667,-0.138666 0.629333,-0.341333 0.138667,-0.106667 0.288,-0.106667 0.117334,0 0.234667,0.08533 0.138667,0.128 0.138667,0.288 0,0.138666 -0.117334,0.245333 -0.352,0.298667 -0.896,0.501333 -0.544,0.192 -1.045333,0.192 -0.853333,0 -1.525333,-0.373333 -0.672001,-0.384 -1.056001,-1.056 -0.373333,-0.682667 -0.373333,-1.536 0,-0.864 0.352,-1.536 0.362667,-0.682667 0.992,-1.056 0.64,-0.384 1.450667,-0.384 z m 0,0.736 q -0.810667,0 -1.344,0.501333 -0.533334,0.501333 -0.650667,1.322667 h 3.904 q -0.08533,-0.821334 -0.597333,-1.322667 -0.512,-0.501333 -1.312,-0.501333 z"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path1330"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ffffff"
+       d="m 71.597402,38.213749 q -0.181333,0 -0.298667,-0.117333 -0.117333,-0.117334 -0.117333,-0.298667 v -5.002667 q 0,-0.181333 0.117333,-0.298667 0.117334,-0.117333 0.298667,-0.117333 0.181333,0 0.288,0.117333 0.117333,0.117334 0.117333,0.298667 v 5.002667 q 0,0.181333 -0.117333,0.298667 -0.106667,0.117333 -0.288,0.117333 z m -0.01067,-6.944 q -0.245333,0 -0.416,-0.170667 -0.16,-0.170667 -0.16,-0.416 0,-0.256 0.170667,-0.416 0.170667,-0.16 0.416,-0.16 0.234667,0 0.405333,0.16 0.170667,0.16 0.170667,0.416 0,0.245333 -0.170667,0.416 -0.170666,0.170667 -0.416,0.170667 z"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path1332"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ffffff"
+       d="m 79.704735,32.688415 q 0.01067,0.032 0.01067,0.096 v 2.506667 q 0,0.832 -0.394667,1.514667 -0.384,0.682667 -1.066666,1.077333 -0.672,0.384 -1.504,0.384 -0.832,0 -1.514667,-0.384 -0.672,-0.394666 -1.066667,-1.077333 -0.384,-0.682667 -0.384,-1.514667 0,-0.832 0.373333,-1.504 0.384,-0.682667 1.034667,-1.066667 0.661334,-0.394666 1.472,-0.394666 0.693334,0 1.269334,0.298666 0.576,0.288 0.949333,0.8 v -3.125333 q 0,-0.181333 0.117333,-0.298667 0.117334,-0.117333 0.298667,-0.117333 0.181333,0 0.288,0.117333 0.117333,0.117334 0.117333,0.298667 z m -2.954666,4.832001 q 0.608,0 1.098666,-0.288 0.490667,-0.298667 0.768,-0.8 0.288,-0.512 0.288,-1.141334 0,-0.181333 -0.01067,-0.266666 -0.01067,-0.02133 -0.01067,-0.05333 -0.106667,-0.832 -0.704,-1.365333 -0.586667,-0.533334 -1.429333,-0.533334 -0.608,0 -1.109334,0.288 -0.490666,0.288 -0.778667,0.8 -0.277333,0.501334 -0.277333,1.130667 0,0.629334 0.277333,1.141334 0.288001,0.501333 0.778667,0.8 0.501334,0.288 1.109334,0.288 z"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path1334"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ffffff"
+       d="m 84.291736,38.267082 q -0.842666,0 -1.525333,-0.384 -0.672,-0.384 -1.056,-1.056 -0.384,-0.682666 -0.384,-1.525333 0,-0.842667 0.384,-1.525334 0.384,-0.682666 1.056,-1.066666 0.682667,-0.384 1.525333,-0.384 0.842667,0 1.514667,0.384 0.672,0.384 1.056,1.066666 0.394667,0.682667 0.394667,1.525334 0,0.842667 -0.384,1.525333 -0.384,0.672 -1.066667,1.056 -0.672,0.384 -1.514667,0.384 z m 0,-0.746666 q 0.618667,0 1.109334,-0.288 0.490667,-0.288 0.768,-0.789334 0.277333,-0.512 0.277333,-1.141333 0,-0.64 -0.277333,-1.141334 -0.277333,-0.512 -0.768,-0.8 -0.490667,-0.288 -1.109334,-0.288 -0.608,0 -1.109333,0.288 -0.490667,0.288 -0.778667,0.8 -0.277333,0.501334 -0.277333,1.141334 0,0.629333 0.277333,1.141333 0.288,0.501334 0.778667,0.789334 0.501333,0.288 1.109333,0.288 z"
+       inkscape:connector-curvature="0" />
+  </g>
+  <g
+     style="fill:#000000"
+     id="g1362"
+     transform="translate(-63.735056,-4.4055096)">
+    <g
+       transform="matrix(0.34610624,0,0,0.34610624,65.735021,6.405648)"
+       style="fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:1"
+       id="g1346">
+      <g
+         style="fill:#000000"
+         transform="translate(-986,-742)"
+         id="g1344">
+        <g
+           transform="translate(986,742)"
+           id="g1342"
+           style="fill:#000000">
+          <path
+             inkscape:connector-curvature="0"
+             d="M 30,50 C 18.972,50 10,41.028 10,30 c 0,-0.33 0.009,-0.664 0.025,-1 0.185,-0.001 0.359,-0.036 0.538,-0.057 0.151,-0.017 0.307,-0.018 0.454,-0.048 0.177,-0.037 0.34,-0.104 0.51,-0.159 0.142,-0.046 0.29,-0.076 0.425,-0.134 0.16,-0.068 0.304,-0.163 0.455,-0.247 0.131,-0.073 0.27,-0.132 0.393,-0.215 0.14,-0.095 0.26,-0.214 0.39,-0.322 0.116,-0.097 0.241,-0.182 0.348,-0.289 0.12,-0.121 0.218,-0.262 0.325,-0.394 0.094,-0.116 0.2,-0.222 0.284,-0.346 0.1,-0.147 0.172,-0.312 0.255,-0.469 0.068,-0.127 0.149,-0.246 0.205,-0.379 0.08,-0.188 0.128,-0.391 0.184,-0.59 0.033,-0.117 0.083,-0.226 0.107,-0.346 C 14.965,24.68 15,24.344 15,24 15,23.702 14.964,23.411 14.911,23.124 14.898,23.047 14.884,22.97 14.867,22.893 14.808,22.64 14.734,22.393 14.637,22.154 14.596,22.053 14.547,21.958 14.5,21.86 14.429,21.712 14.351,21.568 14.265,21.428 14.191,21.305 14.116,21.185 14.032,21.07 13.914,20.908 13.786,20.753 13.649,20.606 13.555,20.505 13.457,20.411 13.355,20.319 13.252,20.224 13.143,20.137 13.031,20.052 12.945,19.985 12.869,19.908 12.778,19.847 16.374,13.748 22.883,10 30,10 41.028,10 50,18.972 50,30 50,41.028 41.028,50 30,50 M 52,30 C 52,17.869 42.131,8 30,8 22.072,8 14.841,12.234 10.918,19.093 10.62,19.037 10.315,19 10,19 7.243,19 5,21.243 5,24 5,26.073 6.269,27.854 8.07,28.611 8.041,29.076 8,29.544 8,30 8,42.131 17.869,52 30,52 42.131,52 52,42.131 52,30"
+             id="path1338"
+             style="fill:#999999" />
+          <path
+             inkscape:connector-curvature="0"
+             d="m 43.2881,54.6436 c -4.071,2.198 -8.646,3.356 -13.288,3.356 -15.44,0 -28,-12.56 -28,-28 0,-15.439 12.56,-28 28,-28 5.296,0 10.454,1.492 14.922,4.314 l 0.26,0.247 c -0.115,0.462 -0.182,0.943 -0.182,1.439 0,3.309 2.691,6 6,6 0.263,0 0.521,-0.022 0.775,-0.055 0.069,-0.009 0.136,-0.02 0.204,-0.032 0.219,-0.036 0.434,-0.083 0.645,-0.142 0.029,-0.008 0.06,-0.011 0.089,-0.02 l 0.255,0.244 c 3.292,4.714 5.032,10.247 5.032,16.005 0,7.001 -2.574,13.645 -7.269,18.811 -0.235,-0.154 -0.484,-0.28 -0.74,-0.392 -0.053,-0.022 -0.107,-0.039 -0.161,-0.06 -0.225,-0.089 -0.456,-0.161 -0.693,-0.216 -0.077,-0.018 -0.152,-0.036 -0.23,-0.051 -0.297,-0.054 -0.599,-0.092 -0.907,-0.092 -2.757,0 -5,2.244 -5,5 0,0.303 0.034,0.603 0.091,0.901 0.03,0.166 0.082,0.323 0.128,0.483 0.026,0.086 0.039,0.175 0.069,0.26 m 16.712,-24.644 c 0,-6.187 -1.877,-12.131 -5.424,-17.193 1.468,-1.095 2.424,-2.839 2.424,-4.807 0,-3.308 -2.691,-6 -6,-6 -2.068,0 -3.894,1.052 -4.974,2.648 -4.797,-3.04 -10.337,-4.648 -16.026,-4.648 -16.542,0 -30,13.458 -30,30 0,16.542 13.458,30 30,30 5.005,0 9.929,-1.27 14.311,-3.654 0.915,1.009 2.223,1.654 3.689,1.654 2.757,0 5,-2.243 5,-5 0,-1.031 -0.315,-1.991 -0.852,-2.788 5.066,-5.542 7.852,-12.682 7.852,-20.212"
+             id="path1340"
+             style="fill:#cccccc" />
+        </g>
+      </g>
+    </g>
+    <path
+       id="path1348"
+       d="m 80.358413,20.752564 c 1.011395,-1.011394 1.011407,-2.64836 0,-3.659767 L 76.23836,12.97275 c -0.611696,-0.611702 -1.45245,-0.851489 -2.245387,-0.723275 -0.0265,0.0037 -0.05257,0.0069 -0.07834,0.01116 -0.0072,0.0012 -0.01521,0.0043 -0.02239,0.0055 -0.40041,0.08094 -0.784723,0.255932 -1.117783,0.52741 -0.06806,0.05515 -0.133515,0.11394 -0.197257,0.177682 -1.010689,1.010689 -1.011407,2.651176 -1.3e-5,3.66257 l 4.120054,4.120054 c 1.012094,1.010707 2.650483,1.009277 3.661171,-0.0012 z m -0.306369,-0.30637 c -0.840208,0.840208 -2.206807,0.840221 -3.047028,0 l -2.060735,-2.060735 3.047015,-3.047015 2.060736,2.060735 c 0.84022,0.84022 0.840208,2.20682 1.2e-5,3.047015 z m -6.431195,-4.083646 c -0.06161,0.08021 -0.184939,0.08344 -0.263017,0.01393 -0.01,-0.0086 -1.112078,-1.024227 -0.549825,-2.418854 0.0086,-0.02435 0.02343,-0.04576 0.04061,-0.06295 0.05085,-0.05085 0.131227,-0.07063 0.201435,-0.04196 0.0967,0.03938 0.142214,0.147452 0.103524,0.24343 -0.466299,1.155374 0.444651,1.993356 0.454705,2.001981 0.07661,0.06945 0.08205,0.187742 0.01257,0.264416 z"
+       style="fill:#333333;stroke-width:0.61343825"
+       inkscape:connector-curvature="0" />
+    <ellipse
+       ry="1.431504"
+       rx="1.4702333"
+       cy="14.731465"
+       cx="69.173622"
+       id="ellipse1350"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.22687662;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:3.68063, 1.22687667;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+    <circle
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.22687662;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:3.68063, 1.22687667;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="circle1352"
+       cx="82.341522"
+       cy="24.742931"
+       r="1.431504" />
+    <circle
+       r="1.7800664"
+       cy="9.2092485"
+       cx="83.351799"
+       id="circle1354"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.22687662;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:3.68063, 1.22687667;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+    <g
+       transform="matrix(0.00220857,0,0,0.00220857,68.563054,10.352346)"
+       id="g1360"
+       style="clip-rule:evenodd;fill:#00aa44;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:204.45423889;stroke-miterlimit:4;stroke-dasharray:none;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision">
+      <g
+         style="fill:#00aa44;fill-opacity:1;stroke:#000000;stroke-width:204.45423889;stroke-miterlimit:4;stroke-dasharray:none"
+         id="g1358">
+        <path
+           style="fill:#008000;fill-opacity:1;stroke:#ffffff;stroke-width:204.45423889;stroke-miterlimit:4;stroke-dasharray:none"
+           inkscape:connector-curvature="0"
+           class="fil0"
+           d="M 2538,4078 5034,1582 c 215,-215 563,-215 778,0 215,215 215,563 0,778 L 2930,5242 c -1,1 -2,2 -3,3 -215,215 -563,215 -778,0 l -104,-104 v 0 L 1015,4111 c -215,-215 -215,-563 0,-778 215,-215 563,-215 778,0 z"
+           id="path1356" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/webeido/webeido/static/no.png b/webeido/webeido/static/no.png
new file mode 100644
index 00000000..040da55c
Binary files /dev/null and b/webeido/webeido/static/no.png differ
diff --git a/webeido/webeido/static/style.css b/webeido/webeido/static/style.css
new file mode 100644
index 00000000..cf37b98b
--- /dev/null
+++ b/webeido/webeido/static/style.css
@@ -0,0 +1,157 @@
+html,
+body {
+  height: 100%;
+}
+
+#vertical {
+    padding: 5px;
+}
+
+#main {
+  overflow: auto;
+  padding-bottom: 40px;
+  padding-top: 20px;
+  /* must be same height as the footer */
+}
+
+#footer {
+  position: relative;
+  margin-top: -40px;
+  /* negative value of footer height */
+  height: 40px;
+  padding-top: 5px;
+  padding-left: 10px;
+}
+
+.divider-15 {
+  /* adds spacing 15px */
+  width:90%;
+  min-height:1px;
+  margin-top:15px;
+  margin-bottom:15px;
+  margin-left:15px;
+  display:inline-block;
+  position:relative;
+}
+
+.divider-vertical {
+  margin-top:15px;
+  /*margin-bottom:15px;*/
+}
+
+span {
+  margin-left: 5px;
+}
+
+div.col-auto {
+  padding-left: 10px;
+  padding-right: 0px;
+}
+
+label.col-form-label {
+  padding-bottom: 0px;
+  padding-top: 0px;
+}
+
+.nav-item {
+  color: white;
+  font-size: 1rem;
+  }
+
+.nav-link {
+  color: white;
+  }
+
+.left_footer {
+  width: 40%;
+  float: left;
+  text-align:left;
+  display: inline-block;
+}
+
+.right_footer {
+  width: 40%;
+  float: right;
+  text-align:right;
+  display: inline-block;
+}
+
+.center_footer {
+  width: 20%;
+  overflow: hidden;
+  display: inline-block;
+  text-align: center;
+}
+
+.wrapper {
+  padding-left: 30px;
+  padding-right: 30px;
+  clear: both;
+  position: relative;
+  overflow: hidden;
+}
+
+div#navbar_links {
+  display: flex;
+}
+
+div.col-12#genome-asset {
+    margin: auto;
+    max-width: 1400px;
+}
+
+div.col-12#txt {
+    margin: auto;
+    max-width: 1400px;
+}
+
+.asset_name {
+    width: 200px;
+}
+
+.asset_desc {
+    width: 500px;
+    min-width: 250px;
+}
+
+.actions {
+    min-width: 180px;
+    width: 180px;
+}
+
+.size {
+    width: 150px;
+    min-width: 150px;
+}
+
+.checksum {
+    width: 280px;
+    min-width: 280px;
+}
+
+.container {
+    max-width: none;
+}
+
+th.footer {
+  font-size:12px;
+  padding: 3px;
+  font-weight: bold;
+  text-align: center;
+  background-color: #464c56;
+  color: white;
+  min-width: 90px;
+}
+
+td.footer {
+  font-size:12px;
+  padding: 3px;
+  text-align: center;
+  color: #464c56;
+  font-weight: bold;
+  font-family: Courier New;
+}
+
+code.footer {
+  color:#464c56;
+}
diff --git a/webeido/webeido/static/yes.png b/webeido/webeido/static/yes.png
new file mode 100644
index 00000000..22105cae
Binary files /dev/null and b/webeido/webeido/static/yes.png differ
diff --git a/webeido/templates/asset.html b/webeido/webeido/templates/asset.html
similarity index 99%
rename from webeido/templates/asset.html
rename to webeido/webeido/templates/asset.html
index 93854a0b..1291fba5 100644
--- a/webeido/templates/asset.html
+++ b/webeido/webeido/templates/asset.html
@@ -69,4 +69,4 @@ <h3>API endpoints</h3>
         </div>
     </div>
 </div>
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/webeido/templates/base.html b/webeido/webeido/templates/base.html
similarity index 99%
rename from webeido/templates/base.html
rename to webeido/webeido/templates/base.html
index 831ebe4a..0744fce2 100644
--- a/webeido/templates/base.html
+++ b/webeido/webeido/templates/base.html
@@ -26,4 +26,4 @@
 		{% include "footer.html" %}
 	</footer>
 
-</html>
\ No newline at end of file
+</html>
diff --git a/webeido/templates/footer.html b/webeido/webeido/templates/footer.html
similarity index 64%
rename from webeido/templates/footer.html
rename to webeido/webeido/templates/footer.html
index fd50fac8..1054e9bb 100644
--- a/webeido/templates/footer.html
+++ b/webeido/webeido/templates/footer.html
@@ -3,18 +3,18 @@
 		<table class="table-responsive">
 					<thead>
 						<tr>
-							<th scope="col" class="footer">refgenieserver</th>
-							<th scope="col" class="footer">refgenconf</th>
+							<th scope="col" class="footer">eido</th>
+							<th scope="col" class="footer">webeido</th>
 							<th scope="col" class="footer">Python</th>
 						</tr>
 					</thead>
 					<tbody>
 						<tr>
 							<td scope="row" class="footer">
-								<code class="footer">{{server_version}}</code>
+								<code class="footer">{{eido_version}}</code>
 							</td>
 							<td scope="row" class="footer">
-								<code class="footer">{{rgc_version}}</code>
+								<code class="footer">{{webeido_version}}</code>
 							</td>
 							<td scope="row" class="footer">
 								<code class="footer">{{python_version}}</code>
@@ -26,6 +26,6 @@
 	<div class="center_footer">
 		<a href="/openapi.json">OpenAPI {{openapi_version}} description</a></div>
 	<div class="right_footer">
-		&copy; 2019-2020 <a href="http://databio.org/" target="_new">Sheffield Computational Biology Lab</a>
+		&copy; 2021 <a href="http://databio.org/" target="_new">Sheffield Computational Biology Lab</a>
 	</div>
-</div>
\ No newline at end of file
+</div>
diff --git a/webeido/webeido/templates/index.html b/webeido/webeido/templates/index.html
new file mode 100644
index 00000000..32b0d23e
--- /dev/null
+++ b/webeido/webeido/templates/index.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{% block title %}Index{% endblock %}
+{% block head %}
+    {{ super() }}
+    <script>
+        $(document).ready(function(){
+          $('[data-toggle="tooltip"]').tooltip();
+        });
+    </script>
+{% endblock %}
+{% block content %}
+{% macro safe_show_asset_attr(x) %}
+    {% if x is none or not x|length %}
+        NA
+    {% else %}
+        {{ x }}
+    {% endif %}
+{% endmacro %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/webeido/webeido/templates/input.html b/webeido/webeido/templates/input.html
new file mode 100644
index 00000000..3bd490ce
--- /dev/null
+++ b/webeido/webeido/templates/input.html
@@ -0,0 +1,183 @@
+{% extends "base.html" %}
+{% block title %}Index{% endblock %}
+{% block head %}
+    {{ super() }}
+    <style>
+    body {
+  font-family: sans-serif;
+}
+a {
+  color: #369;
+}
+.note {
+  width: 500px;
+  margin: 50px auto;
+  font-size: 1.1em;
+  color: #333;
+  text-align: justify;
+}
+#drop-area {
+  border: 2px dashed #ccc;
+  border-radius: 20px;
+  width: 480px;
+  margin: 50px auto;
+  padding: 20px;
+}
+#drop-area.highlight {
+  border-color: purple;
+}
+p {
+  margin-top: 0;
+}
+.my-form {
+  margin-bottom: 10px;
+}
+#gallery {
+  margin-top: 10px;
+}
+#gallery img {
+  width: 150px;
+  margin-bottom: 10px;
+  margin-right: 10px;
+  vertical-align: middle;
+}
+.button {
+  display: inline-block;
+  padding: 10px;
+  background: #ccc;
+  cursor: pointer;
+  border-radius: 5px;
+  border: 1px solid #ccc;
+}
+.button:hover {
+  background: #ddd;
+}
+#fileElem {
+  display: none;
+}
+</style>
+{% endblock %}
+{% block content %}
+<hr class="featurette-divider">
+<div class="container">
+<form action="/validate/" enctype="multipart/form-data" method="post">
+<input name="files" type="file" multiple>
+<input type="submit">
+</form>
+
+<div id="drop-area">
+  <form class="my-form">
+    <p>Upload multiple files with the file dialog or by dragging and dropping images onto the dashed region</p>
+    <input type="file" id="fileElem" multiple accept="image/*" onchange="handleFiles(this.files)">
+    <label class="button" for="fileElem">Select some files</label>
+  </form>  <progress id="progress-bar" max=100 value=0></progress>
+  <div id="gallery" /></div>
+</div>
+
+
+</div><script type="application/javascript">
+console.log("Hello world")
+let dropArea = document.getElementById("drop-area")
+console.log(dropArea)
+
+// Prevent default drag behaviors
+;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
+  dropArea.addEventListener(eventName, preventDefaults, false)   
+  document.body.addEventListener(eventName, preventDefaults, false)
+})
+
+// Highlight drop area when item is dragged over it
+;['dragenter', 'dragover'].forEach(eventName => {
+  dropArea.addEventListener(eventName, highlight, false)
+})
+
+;['dragleave', 'drop'].forEach(eventName => {
+  dropArea.addEventListener(eventName, unhighlight, false)
+})
+
+// Handle dropped files
+dropArea.addEventListener('drop', handleDrop, false)
+
+function preventDefaults (e) {
+  e.preventDefault()
+  e.stopPropagation()
+}
+
+function highlight(e) {
+  dropArea.classList.add('highlight')
+}
+
+function unhighlight(e) {
+  dropArea.classList.remove('active')
+}
+
+function handleDrop(e) {
+  var dt = e.dataTransfer
+  var files = dt.files
+
+  handleFiles(files)
+}
+
+let uploadProgress = []
+let progressBar = document.getElementById('progress-bar')
+
+function initializeProgress(numFiles) {
+  progressBar.value = 0
+  uploadProgress = []
+
+  for(let i = numFiles; i > 0; i--) {
+    uploadProgress.push(0)
+  }
+}
+
+function updateProgress(fileNumber, percent) {
+  uploadProgress[fileNumber] = percent
+  let total = uploadProgress.reduce((tot, curr) => tot + curr, 0) / uploadProgress.length
+  console.debug('update', fileNumber, percent, total)
+  progressBar.value = total
+}
+
+function handleFiles(files) {
+  files = [...files]
+  initializeProgress(files.length)
+  files.forEach(uploadFile)
+  files.forEach(previewFile)
+}
+
+function previewFile(file) {
+  let reader = new FileReader()
+  reader.readAsDataURL(file)
+  reader.onloadend = function() {
+    let img = document.createElement('img')
+    img.src = reader.result
+    document.getElementById('gallery').appendChild(img)
+  }
+}
+
+function uploadFile(file, i) {
+  var url = '/upload'
+  var xhr = new XMLHttpRequest()
+  var formData = new FormData()
+  xhr.open('POST', url, true)
+  xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
+
+  // Update progress (can be used to show progress indicator)
+  xhr.upload.addEventListener("progress", function(e) {
+    updateProgress(i, (e.loaded * 100.0 / e.total) || 100)
+  })
+
+  xhr.addEventListener('readystatechange', function(e) {
+    if (xhr.readyState == 4 && xhr.status == 200) {
+      updateProgress(i, 100) // <- Add this
+    }
+    else if (xhr.readyState == 4 && xhr.status != 200) {
+      // Error. Inform the user
+    }
+  })
+
+  formData.append('upload_preset', 'ujpu6gyk')
+  formData.append('file', file)
+  xhr.send(formData)
+}
+</script>
+{% endblock %}
diff --git a/webeido/templates/navbar.html b/webeido/webeido/templates/navbar.html
similarity index 81%
rename from webeido/templates/navbar.html
rename to webeido/webeido/templates/navbar.html
index 0f327fc5..07fd6950 100644
--- a/webeido/templates/navbar.html
+++ b/webeido/webeido/templates/navbar.html
@@ -8,12 +8,6 @@
 	</a>
 	<div class="collapse navbar-collapse" id="navbarTogglerDemo03">
 		<ul class="navbar-nav mr-auto mt-2 mt-lg-0">
-			<li class="nav-item active">
-				<a class="nav-link" href="/v2/genomes"><b>Genomes</b></a>
-			</li>
-			<li class="nav-item active">
-				<a class="nav-link" href="/v2/assets"><b>Assets</b></a>
-			</li>
 			<li class="nav-item active">
 				<a class="nav-link" href="/docs"><b>API documentation</b></a>
 			</li>
@@ -22,4 +16,4 @@
 		<a class="nav-link" href="https://github.com/pepkit/eido" target="_blank"><i class="fa fa-2x fa-github" aria-hidden="true"></i></a>
 	</div>
 </div>
-</nav>
\ No newline at end of file
+</nav>
diff --git a/webeido/templates/validation_results.html b/webeido/webeido/templates/validation_results.html
similarity index 98%
rename from webeido/templates/validation_results.html
rename to webeido/webeido/templates/validation_results.html
index 11e74ff5..7a495889 100644
--- a/webeido/templates/validation_results.html
+++ b/webeido/webeido/templates/validation_results.html
@@ -26,4 +26,4 @@
     </div>
   </div>
 </div>
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/webeido/uploads/.gitignore b/webeido/webeido/uploads/.gitignore
similarity index 100%
rename from webeido/uploads/.gitignore
rename to webeido/webeido/uploads/.gitignore


Test patch:
diff --git a/.github/workflows/run-pytest.yml b/.github/workflows/run-pytest.yml
index be584bf9..dbdf8a49 100644
--- a/.github/workflows/run-pytest.yml
+++ b/.github/workflows/run-pytest.yml
@@ -1,8 +1,6 @@
 name: Run pytests
 
 on:
-  push:
-    branches: [master, dev]
   pull_request:
     branches: [master, dev]
 
@@ -11,8 +9,8 @@ jobs:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
-        os: [ubuntu-latest, macos-latest]
+        python-version: ["3.6", "3.9", "3.10"]
+        os: [ubuntu-latest]
 
     steps:
     - uses: actions/checkout@v2
@@ -33,9 +31,3 @@ jobs:
 
     - name: Run pytest tests
       run: pytest tests -x -vv --cov=./ --cov-report=xml
-
-    - name: Upload coverage to Codecov
-      uses: codecov/codecov-action@v1
-      with:
-        file: ./coverage.xml
-        name: py-${{ matrix.python-version }}-${{ matrix.os }}
diff --git a/requirements/requirements-test.txt b/requirements/requirements-test.txt
index ecd2dbe5..9f01d7f5 100644
--- a/requirements/requirements-test.txt
+++ b/requirements/requirements-test.txt
@@ -1,4 +1,4 @@
+coveralls
 mock>=2.0.0
 pytest>=4.6.9
 pytest-cov>=2.8.1
-coveralls
\ No newline at end of file
diff --git a/tests/data/peps/test_cfg.yaml b/tests/data/peps/test_cfg.yaml
index 6cc3d189..32f028d7 100644
--- a/tests/data/peps/test_cfg.yaml
+++ b/tests/data/peps/test_cfg.yaml
@@ -1,10 +1,10 @@
 name: test
+pep_version: 2.0.0
+sample_table: test_sample_table.csv
 
-metadata:
-  sample_table: test_sample_table.csv
-  output_dir: test
-
-constant_attributes:
-  organism:
-    "Homo sapiens":
-      genome: hg38
\ No newline at end of file
+sample_modifiers:
+  imply:
+  - if:
+      organism: "Homo sapiens"
+    then:
+      genome: hg38
diff --git a/tests/data/peps/test_sample_table.csv b/tests/data/peps/test_sample_table.csv
index e6e5fa12..d2881012 100644
--- a/tests/data/peps/test_sample_table.csv
+++ b/tests/data/peps/test_sample_table.csv
@@ -1,3 +1,3 @@
 sample_name,protocol,genome
 GSM1558746,GRO,hg38
-GSM1480327,PRO,hg38
\ No newline at end of file
+GSM1480327,PRO,hg38
diff --git a/tests/data/schemas/test_schema.yaml b/tests/data/schemas/test_schema.yaml
index c17568cc..13cdd1d6 100644
--- a/tests/data/schemas/test_schema.yaml
+++ b/tests/data/schemas/test_schema.yaml
@@ -11,12 +11,12 @@ properties:
     items:
       type: object
       properties:
-        sample_name: 
+        sample_name:
           type: string
-        protocol: 
+        protocol:
           type: string
         genome:
           type: string
 
 required:
-  - samples
\ No newline at end of file
+  - samples
diff --git a/tests/data/schemas/test_schema_imports.yaml b/tests/data/schemas/test_schema_imports.yaml
index 3da3be96..3b939d0d 100644
--- a/tests/data/schemas/test_schema_imports.yaml
+++ b/tests/data/schemas/test_schema_imports.yaml
@@ -14,4 +14,4 @@ properties:
       required:
         - my_numeric_attribute
 required:
-  - samples
\ No newline at end of file
+  - samples
diff --git a/tests/data/schemas/test_schema_invalid.yaml b/tests/data/schemas/test_schema_invalid.yaml
index 38c16577..715e8243 100644
--- a/tests/data/schemas/test_schema_invalid.yaml
+++ b/tests/data/schemas/test_schema_invalid.yaml
@@ -22,4 +22,4 @@ properties:
 
 required:
   - samples
-  - invalid
\ No newline at end of file
+  - invalid
diff --git a/tests/data/schemas/test_schema_samples.yaml b/tests/data/schemas/test_schema_samples.yaml
index 38f1da91..13cdd1d6 100644
--- a/tests/data/schemas/test_schema_samples.yaml
+++ b/tests/data/schemas/test_schema_samples.yaml
@@ -19,4 +19,4 @@ properties:
           type: string
 
 required:
-  - samples
\ No newline at end of file
+  - samples
diff --git a/tests/test_conversions.py b/tests/test_conversions.py
index 65881ebb..92f6936c 100644
--- a/tests/test_conversions.py
+++ b/tests/test_conversions.py
@@ -1,6 +1,5 @@
-import pytest
-
 from eido.conversion import *
+import peppy
 
 
 class TestConversionInfrastructure:
@@ -20,3 +19,13 @@ def test_plugins_are_callable(self):
         assert all(
             [callable(plugin_fun) for plugin_name, plugin_fun in avail_plugins.items()]
         )
+
+    def test_basic_filter(self, project_object):
+        conv_result = run_filter(
+            project_object,
+            "basic",
+            verbose=False,
+            plugin_kwargs={"paths": {"project": "out/basic_prj.txt"}},
+        )
+        # the basic filter just converts to a string
+        assert conv_result["project"] == str(project_object)
diff --git a/tests/test_validations.py b/tests/test_validations.py
index 61ffb5f8..ec839f46 100644
--- a/tests/test_validations.py
+++ b/tests/test_validations.py
@@ -1,10 +1,23 @@
+import urllib
+
 import pytest
 from jsonschema.exceptions import ValidationError
+from peppy import Project
 from peppy.utils import load_yaml
 
 from eido import *
 
 
+def _check_remote_file_accessible(url):
+    try:
+        code = urllib.request.urlopen(url).getcode()
+    except:
+        pytest.skip(f"Remote file not found: {url}")
+    else:
+        if code != 200:
+            pytest.skip(f"Return code: {code}. Remote file not found: {url}")
+
+
 class TestProjectValidation:
     def test_validate_works(self, project_object, schema_file_path):
         validate_project(project=project_object, schema=schema_file_path)
@@ -87,6 +100,7 @@ class TestRemoteValidation:
         "schema_url", ["https://schema.databio.org/pep/2.0.0.yaml"]
     )
     def test_validate_works_with_remote_schemas(self, project_object, schema_url):
+        _check_remote_file_accessible(schema_url)
         validate_project(project=project_object, schema=schema_url)
         validate_config(project=project_object, schema=schema_url)
         validate_sample(project=project_object, schema=schema_url, sample_name=0)
@@ -95,3 +109,34 @@ def test_validate_works_with_remote_schemas(self, project_object, schema_url):
 class TestImportsValidation:
     def test_validate(self, project_object, schema_file_path):
         validate_project(project=project_object, schema=schema_file_path)
+
+
+class TestProjectWithoutConfigValidation:
+    @pytest.mark.parametrize(
+        "remote_pep_cfg",
+        [
+            "https://raw.githubusercontent.com/pepkit/example_peps/master/example_basic/sample_table.csv"
+        ],
+    )
+    def test_validate_works(self, schema_file_path, remote_pep_cfg):
+        _check_remote_file_accessible(remote_pep_cfg)
+        validate_project(
+            project=Project(
+                remote_pep_cfg
+            ),  # create Project object from a remote sample table
+            schema=schema_file_path,
+        )
+
+    @pytest.mark.parametrize(
+        "remote_pep_cfg",
+        [
+            "https://raw.githubusercontent.com/pepkit/example_peps/master/example_basic/sample_table.csv"
+        ],
+    )
+    def test_validate_detects_invalid(self, schema_invalid_file_path, remote_pep_cfg):
+        _check_remote_file_accessible(remote_pep_cfg)
+        with pytest.raises(ValidationError):
+            validate_project(
+                project=Project(remote_pep_cfg),
+                schema=schema_invalid_file_path,
+            )