diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..d8918787 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: b8e49ef8cdfa73e7fd64027a7b46a59d +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/Concepts.html b/Concepts.html new file mode 100644 index 00000000..1034059e --- /dev/null +++ b/Concepts.html @@ -0,0 +1,146 @@ + + + + + + + Concepts — AppAPI latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Concepts

+
+

API Access Control Mechanism

+

Each application defines list of API groups it intends to access.

+

This system easily allows you to increase the level of trust in applications. +Even prior to installation, it’s possible to ascertain the API groups to which an application will gain access.

+
+
+

Extensible Deployment

+

The system should support the expansion and integration of new deployment methods, avoiding any tight coupling with a specific deployment type. +Applications should be capable of indicating the deployment methods they can accommodate.

+

Given the evolving landscape of new technologies and the potential emergence of more intricate or simplified deployment options, +the system is architected to seamlessly embrace the integration of novel deployment modes.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/CreationOfDeployDaemon.html b/CreationOfDeployDaemon.html new file mode 100644 index 00000000..a878638d --- /dev/null +++ b/CreationOfDeployDaemon.html @@ -0,0 +1,277 @@ + + + + + + + Creation of Deploy Daemon — AppAPI latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+
+
+

Creation of Deploy Daemon

+

The Deploy Daemon (DaemonConfig) is used to orchestrate the deployment of ExApps.

+
+

Note

+

Currently only docker-install and manual-install deployment methods are supported.

+
+

The recommended daemon configuration is using AppAPI Docker Socket Proxy.

+_images/app_api_3.png +

You can choose one of the basic configuration templates and adjust to your needs.

+
+

Note

+

We highly recommend to use UI to create Deploy Daemons.

+
+
+

Register Deploy daemon form

+
    +
  1. Name: unique name of the Deploy daemon

  2. +
  3. Display name: the name that will be displayed in the UI

  4. +
  5. Deployment method: by default you will need to choose docker_install, manual_install is for development or custom use case of manual ExApp installation

  6. +
  7. Daemon Host: hostname/IP address + port of the Deploy daemon

  8. +
  9. Nextcloud URL: autofilled with current domain, you might need to change the protocol to http/https depending on your setup

  10. +
  11. Set as default daemon: check if you want set new Deploy daemon as default

  12. +
  13. Enable https: check if your Deploy daemon (Docker Socket Proxy) is configured with TLS

  14. +
  15. +
    Deploy Config:
      +
    1. Network: Docker network name, depends on your networking setup, enforces to “host” if “Enable https” is checked

    2. +
    3. HaProxy password: password for Docker Socket Proxy, if it is configured with TLS

    4. +
    5. Compute Device: CPU, CUDA or ROCm, depending on your hardware config on Deploy daemon host machine

    6. +
    7. Add additional option (see Additional options): setup additional KEY + VALUE deploy config options

    8. +
    +
    +
    +
  16. +
+
+

Note

+

For remote DSP setup, it should expose the ports on the host.

+
+
+
+

OCC CLI

+

There are a few commands to manage Deploy Daemons:

+
    +
  1. Register occ app_api:daemon:register

  2. +
  3. Unregister occ app_api:daemon:unregister

  4. +
  5. List registered daemons occ app_api:daemon:list

  6. +
+
+

Register

+

Register Deploy Daemon (DaemonConfig).

+

Command: app_api:daemon:register [--net NET] [--haproxy_password HAPROXY_PASSWORD] [--compute_device COMPUTE_DEVICE] [--set-default] [--] <name> <display-name> <accepts-deploy-id> <protocol> <host> <nextcloud_url>

+
+

Arguments

+
+
    +
  • name - unique name of the daemon (e.g. docker_local_sock)

  • +
  • display-name - name of the daemon (e.g. My Local Docker, will be displayed in the UI)

  • +
  • accepts-deploy-id - type of deployment (docker-install or manual-install)

  • +
  • host - path to docker-socket or the Docker Socket Proxy: address:port

  • +
  • protocol - protocol used to communicate with the Daemon/ExApps (http or https)

  • +
  • nextcloud_url - Nextcloud URL, Daemon config required option (e.g. https://nextcloud.local)

  • +
+
+
+
+

Options

+
+
    +
  • --net [network-name] - [required] network name to bind docker container to (default: host)

  • +
  • --haproxy_password HAPROXY_PASSWORD - [optional] password for AppAPI Docker Socket Proxy

  • +
  • --compute_device GPU - [optional] GPU device to expose to the daemon (e.g. cpu|cuda|rocm, default: cpu)

  • +
  • --set-default - [optional] set created daemon as default for ExApps installation

  • +
+
+
+
+

DeployConfig

+

DeployConfig is a set of additional options in Daemon config, which are used in deployment algorithms to configure +ExApp container.

+
{
+        "net": "host",
+        "nextcloud_url": "https://nextcloud.local",
+        "haproxy_password": "some_secure_password",
+        "computeDevice": {
+                "id": "cuda",
+                "name": "CUDA (NVIDIA)",
+        },
+}
+
+
+
+
+

DeployConfig options

+
+
    +
  • net [required] - network name to bind docker container to (default: host)

  • +
  • nextcloud_url [required] - Nextcloud URL (e.g. https://nextcloud.local)

  • +
  • haproxy_password [optional] - password for AppAPI Docker Socket Proxy

  • +
  • computeDevice [optional] - Compute device to attach to the daemon (e.g. { "id": "cuda", "label": "CUDA (NVIDIA)" })

  • +
+
+
+
+
+

Unregister

+

Unregister Deploy Daemon (DaemonConfig).

+

Command: app_api:daemon:unregister <daemon-config-name>

+
+
+

List registered daemons

+

List registered Deploy Daemons (DaemonConfigs).

+

Command: app_api:daemon:list

+
+
+
+

Nextcloud AIO

+

In case of AppAPI installed in AIO, default Deploy Daemon is registered automatically. +It is possible to register additional Deploy Daemons with the same ways as described above.

+
+
+

Additional options

+

There is a possibility to add additional options to the Deploy Daemon configuration, +which are key-value pairs.

+

Currently, the following options are available:

+
+
    +
  • OVERRIDE_APP_HOST - can be used to override the host that will be used for ExApp binding (not passed to ExApp container envs)

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/DeployConfigurations.html b/DeployConfigurations.html new file mode 100644 index 00000000..1abf780e --- /dev/null +++ b/DeployConfigurations.html @@ -0,0 +1,498 @@ + + + + + + + Deployment configurations — AppAPI latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Deployment configurations

+
+
Currently, only one kind of application deployment is supported:
    +
  • Docker Deploy Daemon

  • +
+
+
+
+

Docker Deploy Daemon

+

Orchestrates the deployment of applications as Docker containers.

+
+

Warning

+

The administrator is responsible for the security actions taken to configure the Docker daemon connected to the Nextcloud instance.

+

These schemes are only examples of possible configurations.

+

We recommend that you use the AppAPI Docker Socket Proxy or AIO Docker Socket Proxy container.

+
+

There are several Docker Daemon Deploy configurations (example schemes):

+
+
    +
  • Nextcloud and Docker on the same host (via socket or DockerSocketProxy)

  • +
  • Nextcloud on the host and Docker on a remote host (via DockerSocketProxy with HTTPS)

  • +
  • Nextcloud and ExApps in the same Docker (via DockerSocketProxy)

  • +
  • Nextcloud in AIO Docker and ExApps in the same Docker (via AIO DockerSocketProxy)

  • +
+
+
+

NC & Docker on the Same-Host

+

The simplest configuration is when Nextcloud is installed on the host and Docker is on the same host and applications are deployed to it.

+
+ stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Nextcloud --> Daemon : /var/run/docker.sock + Daemon --> Containers + + state Containers { + ExApp1 + -- + ExApp2 + -- + ExApp3 + } + } + + class Nextcloud nextcloud + class Daemon docker + class ExApp1 python + class ExApp2 python + class ExApp3 python +
+
Suggested config values(template Custom default):
    +
  1. Daemon host: /var/run/docker.sock

  2. +
  3. HTTPS checkbox: not supported using docker socket

  4. +
  5. Network: host

  6. +
  7. HaProxy password: not supported using raw docker socket, should be empty

  8. +
+
+
+

+

Suggested way to communicate with Docker via Docker Socket Proxy container.

+
+ stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Nextcloud --> DockerSocketProxy: by port + Docker --> Containers + Docker --> DockerSocketProxy : /var/run/docker.sock + + state Containers { + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + DockerSocketProxy --> ExApp3 + } + } + + class Nextcloud nextcloud + class Docker docker + class ExApp1 python + class ExApp2 python + class ExApp3 python +
+
Suggested config values(template Docker Socket Proxy):
    +
  1. +
    Daemon host: localhost:2375
    +
    Choose A or B option:
      +
    1. Docker Socket Proxy should be deployed with network=host and BIND_ADDRESS=127.0.0.1

    2. +
    3. Docker Socket Proxy should be deployed with network=bridge and it’s port should be published to host’s 127.0.0.1(e.g. -p 127.0.0.1:2375:2375)

    4. +
    +
    +
    +
    +
    +
  2. +
  3. HTTPS checkbox: disabled

  4. +
  5. Network: host

  6. +
  7. HaProxy password: should not be empty

  8. +
+
+
+
+

Warning

+

Be careful with option A, by default Docker Socket Proxy binds to * if BIND_ADDRESS is not specified during container creation. +Check opened ports after finishing configuration.

+
+
+
+

Docker on a remote host

+

Distributed configuration occurs when Nextcloud is installed on one host and Docker is located on a remote host, resulting in the deployment of applications on the remote host.

+

Benefit: no performance impact on Nextcloud host.

+

In this case, the AppAPI uses a Docker Socket Proxy deployed on remote host to access docker socket and ExApps.

+
+ stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Direction LR + + Host1 --> Host2 : by port + + state Host1 { + Nextcloud + } + + state Host2 { + [*] --> DockerSocketProxy : by port + Daemon --> Containers + + state Containers { + [*] --> DockerSocketProxy : /var/run/docker.sock + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + DockerSocketProxy --> ExApp3 + } + } + + class Nextcloud nextcloud + class Daemon docker + class ExApp1 python + class ExApp2 python + class ExApp3 python +
+
Suggested config values(template Docker Socket Proxy):
    +
  1. Daemon host: ADDRESS_OF_REMOTE_MACHINE (e.g. server_name.com:2375)

  2. +
  3. HTTPS checkbox: enabled

  4. +
  5. Network: host

  6. +
  7. HaProxy password: should not be empty

  8. +
+
+
+
+
+

NC & ExApps in the same Docker

+

Applications are deployed in the same docker where Nextcloud resides.

+

Suggested way to communicate with Docker: via docker-socket-proxy.

+
+ stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Daemon --> Containers + + state Containers { + [*] --> DockerSocketProxy : /var/run/docker.sock + Nextcloud --> DockerSocketProxy: by port + -- + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + } + } + + class Nextcloud nextcloud + class Daemon docker + class ExApp1 python + class ExApp2 python + class ExApp3 python +
+
Suggested config values(template Docker Socket Proxy):
    +
  1. Daemon host: nextcloud-appapi-dsp:2375

  2. +
  3. HTTPS checkbox: disabled

  4. +
  5. Network: user defined network

  6. +
  7. HaProxy password: should not be empty

  8. +
+
+
+
+

Note

+

Network should not be the default docker’s bridge as it does not support DNS resolving by container names.

+

This means that Docker Socket Proxy, Nextcloud and ExApps containers should all be in the same docker network, different from the default bridge.

+
+
+
+

Nextcloud in Docker AIO (all-in-one)

+

In case of AppAPI is in Docker AIO setup (installed in Nextcloud container).

+
+

Note

+

AIO Docker Socket Proxy container must be enabled.

+
+
+ stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef docker2 fill: #1f97ee, color: transparent, font-size: 20px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Daemon --> Containers + + state Containers { + [*] --> NextcloudAIOMasterContainer : /var/run/docker.sock + [*] --> DockerSocketProxy : /var/run/docker.sock + NextcloudAIOMasterContainer --> Nextcloud + AppAPI --> Nextcloud : installed in + Nextcloud --> DockerSocketProxy + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + DockerSocketProxy --> ExApp3 + } + } + + class Nextcloud nextcloud + class Daemon docker + class Daemon2 docker2 + class ExApp1 python + class ExApp2 python + class ExApp3 python +

AppAPI will automatically create default default DaemonConfig to use AIO Docker Socket Proxy as orchestrator to create ExApp containers.

+
+

Note

+

Default DaemonConfig will be created only if the default DaemonConfig is not already registered.

+
+
+

Default AIO Deploy Daemon

+

Nextcloud AIO has a specifically created Docker Socket Proxy container to be used as the Deploy Daemon in AppAPI. +It has fixed parameters:

+
    +
  • Name: docker_aio

  • +
  • Display name: AIO Docker Socket Proxy

  • +
  • Accepts Deploy ID: docker-install

  • +
  • Protocol: http

  • +
  • Host: nextcloud-aio-docker-socket-proxy:2375

  • +
  • Compute device: CPU

  • +
  • Network: nextcloud-aio

  • +
  • Nextcloud URL (passed to ExApps): https://$NC_DOMAIN

  • +
+
+
+

Docker Socket Proxy security

+

AIO Docker Socket Proxy has strictly limited access to the Docker APIs described in HAProxy configuration.

+
+
+
+
+

NC to ExApp Communication

+

Each type of DeployDaemon necessarily implements the resolveExAppUrl function.

+

It has such prototype:

+
public function resolveExAppUrl(
+        string $appId, string $protocol, string $host, array $deployConfig, int $port, array &$auth
+) {}
+
+
+

where:

+
    +
  • protocol is daemon protocol value

  • +
  • host is daemon host value, can be DNS:port or IP:PORT or even path to docker socket.

  • +
  • port is an integer with ExApp port

  • +
  • deployConfig can be custom for each Daemon type

  • +
  • auth is an optional array, with Basic Authentication data if needed to access ExApp

  • +
+
+

Note

+

Starting with AppAPI version 2.5.0, the optional additional parameter OVERRIDE_APP_HOST can be used to +override the host that will be used for ExApp binding.

+

It can be 0.0.0.0 in some specific configurations, when VPN is used +or both Nextcloud instance and ExApps are one the same physical machine but different virtual environments.

+

Also you can specify something like 10.10.2.5 and in this case ExApp wil try to bind to that address and +AppAPI will try to send request s directly to this address assuming that ExApp itself bound on it.

+
+

The simplest implementation is in Manual-Install deploy type:

+
public function resolveExAppUrl(
+        string $appId, string $protocol, string $host, array $deployConfig, int $port, array &$auth
+): string {
+        $auth = [];
+        if (isset($deployConfig['additional_options']['OVERRIDE_APP_HOST']) &&
+                $deployConfig['additional_options']['OVERRIDE_APP_HOST'] !== ''
+        ) {
+                $wideNetworkAddresses = ['0.0.0.0', '127.0.0.1', '::', '::1'];
+                if (!in_array($deployConfig['additional_options']['OVERRIDE_APP_HOST'], $wideNetworkAddresses)) {
+                        $host = $deployConfig['additional_options']['OVERRIDE_APP_HOST'];
+                }
+        }
+        return sprintf('%s://%s:%s', $protocol, $host, $port);
+}
+
+
+

Here we see that AppAPI send requests to host:port specified during daemon creation.

+

Now let’s take a look at the Docker Daemon implementation of resolveExAppUrl:

+
public function resolveExAppUrl(
+        string $appId, string $protocol, string $host, array $deployConfig, int $port, array &$auth
+): string {
+        $auth = [];
+        if (isset($deployConfig['additional_options']['OVERRIDE_APP_HOST']) &&
+                $deployConfig['additional_options']['OVERRIDE_APP_HOST'] !== ''
+        ) {
+                $wideNetworkAddresses = ['0.0.0.0', '127.0.0.1', '::', '::1'];
+                if (!in_array($deployConfig['additional_options']['OVERRIDE_APP_HOST'], $wideNetworkAddresses)) {
+                        return sprintf(
+                                '%s://%s:%s', $protocol, $deployConfig['additional_options']['OVERRIDE_APP_HOST'], $port
+                        );
+                }
+        }
+        $host = explode(':', $host)[0];
+        if ($protocol == 'https') {
+                $exAppHost = $host;
+        } elseif (isset($deployConfig['net']) && $deployConfig['net'] === 'host') {
+                $exAppHost = 'localhost';
+        } else {
+                $exAppHost = $appId;
+        }
+        if (isset($deployConfig['haproxy_password']) && $deployConfig['haproxy_password'] !== '') {
+                $auth = [self::APP_API_HAPROXY_USER, $deployConfig['haproxy_password']];
+        }
+        return sprintf('%s://%s:%s', $protocol, $exAppHost, $port);
+}
+
+
+

Here we have much more complex algorithm of detecting to where requests should be send.

+

First of all if protocol is set to https AppAPI always send requests to daemon host, +and this is in case of https it is a HaProxy that will forward requests to ExApps that will be listen on localhost

+

Briefly it will look like this(haproxy_host==daemon host value):

+

NC –> https –> haproxy_host:ex_app_port –> http –> localhost:ex_app_port

+

When protocol is not https but http, then what will be the endpoint where to send requests is determined by $deployConfig['net'] value.

+

If net is defined and equal to host then AppAPI assumes that ExApp is installed somewhere in the current host network and will be available on localhost loop-back adapter.

+

NC –> http –> localhost:ex_app_port

+

In all other cases ExApp should be available by it’s name: e.g. when using docker custom bridge network all containers available by DNS.

+

NC –> http –> app_container_name:ex_app_port

+

This three different types of communication covers all most popular configurations.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/DevSetup.html b/DevSetup.html new file mode 100644 index 00000000..02a10d14 --- /dev/null +++ b/DevSetup.html @@ -0,0 +1,183 @@ + + + + + + + Setting up dev environment — AppAPI latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Setting up dev environment

+

We highly recommend using Julius Haertl docker setup for the Nextcloud dev setup.

+

Suggested IDE: PhpStorm, though you can certainly use any IDE of your preference such as VS Code or Vim.

+
+

Get last version from GitHub

+

Assuming you’re in the apps folder of Nextcloud with command git:

+
git clone https://github.com/cloud-py-api/app_api.git
+
+
+

Change to the app_api directory with shell:

+
cd app_api
+
+
+

Then, build frontend assets in development mode with shell:

+
npm ci && npm run dev
+
+
+

After this, you can enable it from the directory where the occ command resides, with shell:

+
./occ app:enable --force app_api
+
+
+
+
+

In Place of a Conclusion

+

There are several make commands available to ease frequent development actions.

+

To see the complete list, execute make help.

+
+

Docker remote API

+

The Docker Engine remote API can be easily configured via make dock2port command. +The command will create a docker container to provide remote Docker Engine API.

+

Afterward, register DaemonConfigs in Nextcloud using make dock-port command.

+
+
+

Docker by socket

+

For Docker via socket, use the command make dock-sock. +This registers DaemonConfigs in Nextcloud for the default socket connection (/var/run/docker.sock).

+

Make sure that socket has enough permissions for Nextcloud and webserver user to access it +and actually forwarded to the container:

+
...
+volumes:
+        ...
+        - /var/run/docker.sock:/var/run/docker.sock
+        ...
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Installation.html b/Installation.html new file mode 100644 index 00000000..4f03d0b0 --- /dev/null +++ b/Installation.html @@ -0,0 +1,225 @@ + + + + + + + Installation — AppAPI latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installation

+

There are two ways to install the AppAPI: from the AppStore or from the source code.

+
+

Note

+

AppAPI 3.0.0 is the last version supported Nextcloud 27.

+
+
+

Installation from the AppStore

+

Simply navigate to the Apps management page in your Nextcloud and setup the AppAPI from the Tools category.

+
+
+

Installation from the source code

+

To install the AppAPI from the source code, follow these steps:

+
+

1. Clone the AppAPI repository into your apps directory

+

Clone the latest main branch:

+
+
git clone https://github.com/cloud-py-api/app_api.git
+
+
+
+

or clone a specific version by specifying the version tag:

+
+
git clone https://github.com/cloud-py-api/app_api.git --branch <version-tag>
+
+
+
+

where <version-tag> is the version you want to install.

+
+
+

2. Build frontend assets in production mode

+
+
npm ci && npm run build
+
+
+
+
+
+

3. Enable the AppAPI

+
+
./occ app:enable --force app_api
+
+
+
+

To install it in development mode, follow the instructions on this page: Setting up dev environment.

+
+
+

4. Setup Deploy daemon

+

Upon the successful installation of the AppAPI, a one-time configuration is essential. +Details on this configuration can be found in the subsequent section: Creation of Deploy Daemon.

+
+
+

4.1 Deploy daemon configuration

+

Deploy daemon configuration steps:

+
    +
  1. Go to the AppAPI admin settings.

  2. +
  3. Click on the “Register Daemon” button.

  4. +
  5. +
    Fill in the required fields:
      +
    • Name: unique name of the Deploy daemon

    • +
    • Display name: the name that will be displayed in the UI

    • +
    • Deployment method: by default you will need to choose docker_install, manual_install is for development or custom use case of manual ExApp installation

    • +
    • Daemon Host: hostname/IP address + port of the Deploy daemon

    • +
    • Nextcloud URL: autofilled with current domain, you might need to change the protocol to http/https depending on your setup

    • +
    • Set as default daemon: check if you want set new Deploy daemon as default

    • +
    • Enable https: check if your Deploy daemon (Docker Socket Proxy) is configured with TLS

    • +
    • +
      Deploy Config:
        +
      • Network: Docker network name, depends on your networking setup, enforces to “host” if “Enable https” is checked

      • +
      • HaProxy password: password for Docker Socket Proxy, if it is configured with TLS

      • +
      • Compute Device: CPU, CUDA or ROCm, depending on your hardware config on Deploy daemon host machine

      • +
      • Add additional option (see Additional options): setup additional KEY + VALUE deploy config options

      • +
      +
      +
      +
    • +
    +
    +
    +
  6. +
  7. Check connection: to verify configuration is correct

  8. +
  9. Register: to save the Deploy daemon configuration

  10. +
+

Deployment configuration examples can be found here.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/ManagingExternalApplications.html b/ManagingExternalApplications.html new file mode 100644 index 00000000..fcaab798 --- /dev/null +++ b/ManagingExternalApplications.html @@ -0,0 +1,277 @@ + + + + + + + Managing External Applications — AppAPI latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Managing External Applications

+

There are two ways to manage ExApps:

+
    +
  1. Using OCC CLI tool

  2. +
  3. Using the ExApp Management UI

  4. +
+
+

OCC CLI

+

There are several commands to work with ExApps:

+
    +
  1. Register

  2. +
  3. Unregister

  4. +
  5. Update

  6. +
  7. Enable

  8. +
  9. Disable

  10. +
  11. List ExApps

  12. +
  13. List ExApp users (removed since AppAPI 3.0.0)

  14. +
  15. List ExApp scopes

  16. +
+
+

Register

+

Command: app_api:app:register [--force-scopes] [--info-xml INFO-XML] [--json-info JSON-INFO] [--] <appid> <daemon-config-name>

+

The register command is the first ExApp installation step.

+
+

Arguments

+
+
    +
  • appid - unique name of the ExApp (e.g. app_python_skeleton, must be the same as in deployed container)

  • +
  • daemon-config-name - unique name of the daemon (e.g. docker_local_sock)

  • +
+
+
+
+

Options

+
+
    +
  • --force-scopes [optional] - force scopes approval

  • +
  • --json-info JSON-INFO [optional] - ExApp deploy JSON info (json string)

  • +
  • --info-xml INFO-XML [optional] - path to info.xml file (url or local absolute path)

  • +
+
+
+
+
+

Unregister

+

Command: app_api:app:unregister [--rm-data] [--force] [--silent] [--] <appid>

+

To remove an ExApp you can use the unregister command. +There are additional options to keep the ExApp persistent storage (data volume).

+
+

Arguments

+
+
    +
  • appid - unique name of the ExApp (e.g. app_python_skeleton, must be the same as in deployed container)

  • +
+
+
+
+

Options

+
+
    +
  • +
    --rm-data [optional] - remove ExApp persistent storage (data volume)
      +
    • --force [optional] - continue removal even if some error occurs.

    • +
    • --silent [optional] - print a minimum of information, display only some errors, if any.

    • +
    +
    +
    +
  • +
+
+
+
+
+

Update

+

Command: app_api:app:update [--info-xml INFO-XML] [--force-update] [--force-scopes] [-e|--enabled] [--] <appid>

+

ExApp will be updated if there is a new version available.

+
+

Arguments

+
+
    +
  • appid - unique name of the ExApp (e.g. app_python_skeleton, must be the same as in deployed container)

  • +
+
+
+
+

Options

+
+
    +
  • --info-xml INFO-XML [optional] - path to info.xml file (url or local absolute path)

  • +
  • --force-update [optional] - force ExApp update (do not prompt for confirmation)

  • +
  • --force-scopes [optional] - force scopes approval (accept all scopes)

  • +
  • -e|--enabled [optional] - enable ExApp after update

  • +
+
+
+
+
+

Enable

+

Command: app_api:app:enable <appid>

+
+
+

Disable

+

Command: app_api:app:disable <appid>

+
+
+

List ExApps

+

Command: app_api:app:list

+

ListExApps command will show all ExApps:

+
ExApps:
+appid (Display Name): version [enabled/disabled]
+to_gif_example (To Gif Example): 1.0.0 [enabled]
+upscaler_example (Upscaler Example): 1.0.0 [enabled]
+
+
+
+
+
+

Using the ExApp Management UI

+

ExApps management is similar to default Apps management. +To access ExApps management navigate using Admin settings dropdown menu or from AppAPI admin settings section.

+
+

Note

+

ExApps management support only apps from App Store. For manual-install type use CLI ExApps management commands.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/TestDeploy.html b/TestDeploy.html new file mode 100644 index 00000000..5b7c0d85 --- /dev/null +++ b/TestDeploy.html @@ -0,0 +1,225 @@ + + + + + + + Test Deploy Daemon — AppAPI latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Test Deploy Daemon

+

You can test each Daemon configuration deployment from the AppAPI Admin settings.

+_images/test_deploy.png +
+

Status Checks

+

The Deploy test installs a test-deploy ExApp +to verify each step of the deployment process, including a hardware support check - +for each compute device, there is a separate Docker image.

+
+

Note

+

The Test Deploy ExApp container is not removed after the test as it’s needed for logs and status checks. +You can remove it after testing from the External Apps page. +The Docker images are also not removed from the Daemon; you can clean up unused images with the docker image prune command.

+
+_images/test_deploy_modal_4.png +
+

Register

+

The Register step is the first step; it checks if the ExApp is registered in Nextcloud.

+
+
+

Image Pull

+

The Image Pull step downloads the ExApp Docker image.

+

Possible errors:

+
    +
  • Image not found (e.g. not public, no image found for your hardware architecture)

  • +
  • Image pull failed (e.g., due to network issues)

  • +
  • Image pull timeout

  • +
  • Your Docker Socket Proxy is not configured correctly and blocks access to this Docker Engine API

  • +
+
+
+

Container Started

+

The Container Started step verifies that the ExApp container is created and started successfully.

+

Possible errors:

+ +
+
+

Heartbeat

+

The Heartbeat step checks if the container’s health check is finished and the container is healthy. +The ExApp might have additional pre-configuration logic during this step.

+

Possible errors:

+
    +
  • ExApp failed to start a web server, e.g., if the port is already in use (this should be visible in the container logs)

  • +
  • ExApp heartbeat_count keeps increasing, this may indicate that the ExApp couldn’t start properly

  • +
  • Nextcloud can not reach the ExApp container, e.g., due to a network issue or a firewall

  • +
+
+
+

Init

+

The Init step checks if the ExApp is initialized and ready to use. +During the init step, the ExApp may perform downloads of extra stuff required for it.

+

Possible errors:

+
    +
  • Initialization failed (e.g., due to network issues or timeout)

  • +
+
+
+

Enabled

+

The Enabled step checks if the ExApp is enabled and ready to use. +During this step, the ExApp registers all the required and available APIs of the Nextcloud AppFramework.

+

Possible errors:

+
    +
  • ExApp did not respond to the enable request

  • +
  • ExApp failed to enable due to a failure in registering AppAPI Nextcloud AppFramework APIs (this should be visible both in the container logs and in the Nextcloud logs if there are any errors)

  • +
+
+
+
+

Download Logs

+

You can download the logs of the last test deploy attempt container.

+
+

Note

+

Downloading Docker container logs is only possible for containers using the json-file or journald logging drivers.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_images/app_api_3.png b/_images/app_api_3.png new file mode 100644 index 00000000..aa01934d Binary files /dev/null and b/_images/app_api_3.png differ diff --git a/_images/test_deploy.png b/_images/test_deploy.png new file mode 100644 index 00000000..2ca76c1c Binary files /dev/null and b/_images/test_deploy.png differ diff --git a/_images/test_deploy_modal_4.png b/_images/test_deploy_modal_4.png new file mode 100644 index 00000000..42fd3b91 Binary files /dev/null and b/_images/test_deploy_modal_4.png differ diff --git a/_sources/Concepts.rst.txt b/_sources/Concepts.rst.txt new file mode 100644 index 00000000..c944cc69 --- /dev/null +++ b/_sources/Concepts.rst.txt @@ -0,0 +1,19 @@ +Concepts +======== + +API Access Control Mechanism +---------------------------- + +Each application defines list of API groups it intends to access. + +This system easily allows you to increase the level of trust in applications. +Even prior to installation, it's possible to ascertain the API groups to which an application will gain access. + +Extensible Deployment +--------------------- + +The system should support the expansion and integration of new deployment methods, avoiding any tight coupling with a specific deployment type. +Applications should be capable of indicating the deployment methods they can accommodate. + +Given the evolving landscape of new technologies and the potential emergence of more intricate or simplified deployment options, +the system is architected to seamlessly embrace the integration of novel deployment modes. diff --git a/_sources/CreationOfDeployDaemon.rst.txt b/_sources/CreationOfDeployDaemon.rst.txt new file mode 100644 index 00000000..bb6f6c6f --- /dev/null +++ b/_sources/CreationOfDeployDaemon.rst.txt @@ -0,0 +1,135 @@ + .. _create-deploy-daemon: + +Creation of Deploy Daemon +========================= + +The Deploy Daemon (DaemonConfig) is used to orchestrate the deployment of ExApps. + +.. note:: + + Currently only ``docker-install`` and ``manual-install`` deployment methods are supported. + +The recommended daemon configuration is using `AppAPI Docker Socket Proxy `_. + +.. image:: ../screenshots/app_api_3.png + + +You can choose one of the basic configuration templates and adjust to your needs. + +.. note:: We highly recommend to use UI to create Deploy Daemons. + +Register Deploy daemon form +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. ``Name``: unique name of the Deploy daemon +2. ``Display name``: the name that will be displayed in the UI +3. ``Deployment method``: by default you will need to choose ``docker_install``, ``manual_install`` is for development or custom use case of manual ExApp installation +4. ``Daemon Host``: hostname/IP address + port of the Deploy daemon +5. ``Nextcloud URL``: autofilled with current domain, you might need to change the protocol to http/https depending on your setup +6. ``Set as default daemon``: check if you want set new Deploy daemon as default +7. ``Enable https``: check if your Deploy daemon (Docker Socket Proxy) is configured with TLS +8. Deploy Config: + 9. ``Network``: Docker network name, depends on your networking setup, enforces to "host" if "Enable https" is checked + 10. ``HaProxy password``: password for Docker Socket Proxy, if it is configured with TLS + 11. ``Compute Device``: CPU, CUDA or ROCm, depending on your hardware config on Deploy daemon host machine + 12. ``Add additional option`` (see :ref:`additional_options_list`): setup additional KEY + VALUE deploy config options + +.. note:: + + For remote DSP setup, it should expose the ports on the host. + + +.. _create-deploy-daemon-cli: + +OCC CLI +^^^^^^^ + +There are a few commands to manage Deploy Daemons: + +1. Register ``occ app_api:daemon:register`` +2. Unregister ``occ app_api:daemon:unregister`` +3. List registered daemons ``occ app_api:daemon:list`` + +Register +-------- + +Register Deploy Daemon (DaemonConfig). + +Command: ``app_api:daemon:register [--net NET] [--haproxy_password HAPROXY_PASSWORD] [--compute_device COMPUTE_DEVICE] [--set-default] [--] `` + +Arguments +********* + + * ``name`` - unique name of the daemon (e.g. ``docker_local_sock``) + * ``display-name`` - name of the daemon (e.g. ``My Local Docker``, will be displayed in the UI) + * ``accepts-deploy-id`` - type of deployment (``docker-install`` or ``manual-install``) + * ``host`` - **path to docker-socket** or the Docker Socket Proxy: ``address:port`` + * ``protocol`` - protocol used to communicate with the Daemon/ExApps (``http`` or ``https``) + * ``nextcloud_url`` - Nextcloud URL, Daemon config required option (e.g. ``https://nextcloud.local``) + +Options +******* + + * ``--net [network-name]`` - ``[required]`` network name to bind docker container to (default: ``host``) + * ``--haproxy_password HAPROXY_PASSWORD`` - ``[optional]`` password for AppAPI Docker Socket Proxy + * ``--compute_device GPU`` - ``[optional]`` GPU device to expose to the daemon (e.g. ``cpu|cuda|rocm``, default: ``cpu``) + * ``--set-default`` - ``[optional]`` set created daemon as default for ExApps installation + +DeployConfig +************ + +DeployConfig is a set of additional options in Daemon config, which are used in deployment algorithms to configure +ExApp container. + +.. code-block:: json + + { + "net": "host", + "nextcloud_url": "https://nextcloud.local", + "haproxy_password": "some_secure_password", + "computeDevice": { + "id": "cuda", + "name": "CUDA (NVIDIA)", + }, + } + +DeployConfig options +******************** + + * ``net`` **[required]** - network name to bind docker container to (default: ``host``) + * ``nextcloud_url`` **[required]** - Nextcloud URL (e.g. ``https://nextcloud.local``) + * ``haproxy_password`` *[optional]* - password for AppAPI Docker Socket Proxy + * ``computeDevice`` *[optional]* - Compute device to attach to the daemon (e.g. ``{ "id": "cuda", "label": "CUDA (NVIDIA)" }``) + +Unregister +---------- + +Unregister Deploy Daemon (DaemonConfig). + +Command: ``app_api:daemon:unregister `` + +List registered daemons +----------------------- + +List registered Deploy Daemons (DaemonConfigs). + +Command: ``app_api:daemon:list`` + +Nextcloud AIO +^^^^^^^^^^^^^ + +In case of AppAPI installed in AIO, default Deploy Daemon is registered automatically. +It is possible to register additional Deploy Daemons with the same ways as described above. + + +.. _additional_options_list: + +Additional options +^^^^^^^^^^^^^^^^^^ + +There is a possibility to add additional options to the Deploy Daemon configuration, +which are key-value pairs. + +Currently, the following options are available: + + - ``OVERRIDE_APP_HOST`` - can be used to override the host that will be used for ExApp binding (not passed to ExApp container envs) diff --git a/_sources/DeployConfigurations.rst.txt b/_sources/DeployConfigurations.rst.txt new file mode 100644 index 00000000..32a71952 --- /dev/null +++ b/_sources/DeployConfigurations.rst.txt @@ -0,0 +1,384 @@ +.. _deploy-configs: + +Deployment configurations +========================= + +Currently, only one kind of application deployment is supported: + * **Docker Deploy Daemon** + +Docker Deploy Daemon +-------------------- + +Orchestrates the deployment of applications as Docker containers. + +.. warning:: + + The administrator is responsible for the security actions taken to configure the Docker daemon connected to the Nextcloud instance. + + These schemes are only examples of possible configurations. + + We recommend that you use the `AppAPI Docker Socket Proxy `_ or `AIO Docker Socket Proxy <#nextcloud-in-docker-aio-all-in-one>`_ container. + +There are several Docker Daemon Deploy configurations (example schemes): + + * Nextcloud and Docker on the **same host** (via socket or DockerSocketProxy) + * Nextcloud on the host and Docker on a **remote** host (via DockerSocketProxy with HTTPS) + * Nextcloud and **ExApps** in the **same Docker** (via DockerSocketProxy) + * Nextcloud in AIO Docker and **ExApps** in the **same Docker** (via AIO DockerSocketProxy) + + +NC & Docker on the Same-Host +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The simplest configuration is when Nextcloud is installed on the host and Docker is on the same host and applications are deployed to it. + +.. mermaid:: + + stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Nextcloud --> Daemon : /var/run/docker.sock + Daemon --> Containers + + state Containers { + ExApp1 + -- + ExApp2 + -- + ExApp3 + } + } + + class Nextcloud nextcloud + class Daemon docker + class ExApp1 python + class ExApp2 python + class ExApp3 python + +Suggested config values(template *Custom default*): + 1. Daemon host: ``/var/run/docker.sock`` + 2. HTTPS checkbox: *not supported using docker socket* + 3. Network: ``host`` + 4. HaProxy password: **not supported using raw docker socket, should be empty** + +--- + +Suggested way to communicate with Docker via `Docker Socket Proxy container `_. + +.. mermaid:: + + stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Nextcloud --> DockerSocketProxy: by port + Docker --> Containers + Docker --> DockerSocketProxy : /var/run/docker.sock + + state Containers { + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + DockerSocketProxy --> ExApp3 + } + } + + class Nextcloud nextcloud + class Docker docker + class ExApp1 python + class ExApp2 python + class ExApp3 python + +Suggested config values(template *Docker Socket Proxy*): + 1. Daemon host: ``localhost:2375`` + Choose **A** or **B** option: + A. Docker Socket Proxy should be deployed with ``network=host`` and ``BIND_ADDRESS=127.0.0.1`` + B. Docker Socket Proxy should be deployed with ``network=bridge`` and it's port should be published to host's 127.0.0.1(e.g. **-p 127.0.0.1:2375:2375**) + 2. HTTPS checkbox: **disabled** + 3. Network: ``host`` + 4. HaProxy password: **should not be empty** + +.. warning:: + + Be careful with option ``A``, by default **Docker Socket Proxy** binds to ``*`` if ``BIND_ADDRESS`` is not specified during container creation. + Check opened ports after finishing configuration. + + +Docker on a remote host +^^^^^^^^^^^^^^^^^^^^^^^ + +Distributed configuration occurs when Nextcloud is installed on one host and Docker is located on a remote host, resulting in the deployment of applications on the remote host. + +Benefit: no performance impact on Nextcloud host. + +In this case, the AppAPI uses a Docker Socket Proxy deployed on remote host to access docker socket and ExApps. + +.. mermaid:: + + stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Direction LR + + Host1 --> Host2 : by port + + state Host1 { + Nextcloud + } + + state Host2 { + [*] --> DockerSocketProxy : by port + Daemon --> Containers + + state Containers { + [*] --> DockerSocketProxy : /var/run/docker.sock + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + DockerSocketProxy --> ExApp3 + } + } + + class Nextcloud nextcloud + class Daemon docker + class ExApp1 python + class ExApp2 python + class ExApp3 python + +Suggested config values(template *Docker Socket Proxy*): + 1. Daemon host: ADDRESS_OF_REMOTE_MACHINE (e.g. **server_name.com:2375**) + 2. HTTPS checkbox: ``enabled`` + 3. Network: ``host`` + 4. HaProxy password: **should not be empty** + +NC & ExApps in the same Docker +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Applications are deployed in the same docker where Nextcloud resides. + +Suggested way to communicate with Docker: via ``docker-socket-proxy``. + +.. mermaid:: + + stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Daemon --> Containers + + state Containers { + [*] --> DockerSocketProxy : /var/run/docker.sock + Nextcloud --> DockerSocketProxy: by port + -- + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + } + } + + class Nextcloud nextcloud + class Daemon docker + class ExApp1 python + class ExApp2 python + class ExApp3 python + +Suggested config values(template *Docker Socket Proxy*): + 1. Daemon host: nextcloud-appapi-dsp:2375 + 2. HTTPS checkbox: ``disabled`` + 3. Network: `user defined network `_ + 4. HaProxy password: **should not be empty** + +.. note:: + Network **should not be the default docker's bridge** as it does not support DNS resolving by container names. + + This means that **Docker Socket Proxy**, **Nextcloud** and **ExApps** containers should all be in the same docker network, different from the default **bridge**. + + +.. _nextcloud-in-docker-aio-all-in-one: + +Nextcloud in Docker AIO (all-in-one) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In case of AppAPI is in Docker AIO setup (installed in Nextcloud container). + +.. note:: + + AIO Docker Socket Proxy container must be enabled. + +.. mermaid:: + + stateDiagram-v2 + classDef docker fill: #1f97ee, color: transparent, font-size: 34px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef docker2 fill: #1f97ee, color: transparent, font-size: 20px, stroke: #364c53, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/docker.png) no-repeat center center / contain + classDef nextcloud fill: #006aa3, color: transparent, font-size: 34px, stroke: #045987, stroke-width: 1px, background: url(https://raw.githubusercontent.com/cloud-py-api/app_api/main/docs/img/nextcloud.svg) no-repeat center center / contain + classDef python fill: #1e415f, color: white, stroke: #364c53, stroke-width: 1px + + Host + + state Host { + Daemon --> Containers + + state Containers { + [*] --> NextcloudAIOMasterContainer : /var/run/docker.sock + [*] --> DockerSocketProxy : /var/run/docker.sock + NextcloudAIOMasterContainer --> Nextcloud + AppAPI --> Nextcloud : installed in + Nextcloud --> DockerSocketProxy + DockerSocketProxy --> ExApp1 + DockerSocketProxy --> ExApp2 + DockerSocketProxy --> ExApp3 + } + } + + class Nextcloud nextcloud + class Daemon docker + class Daemon2 docker2 + class ExApp1 python + class ExApp2 python + class ExApp3 python + +AppAPI will automatically create default default DaemonConfig to use AIO Docker Socket Proxy as orchestrator to create ExApp containers. + +.. note:: + + Default DaemonConfig will be created only if the default DaemonConfig is not already registered. + + +Default AIO Deploy Daemon +************************* + +Nextcloud AIO has a specifically created Docker Socket Proxy container to be used as the Deploy Daemon in AppAPI. +It has `fixed parameters `_: + +* Name: ``docker_aio`` +* Display name: ``AIO Docker Socket Proxy`` +* Accepts Deploy ID: ``docker-install`` +* Protocol: ``http`` +* Host: ``nextcloud-aio-docker-socket-proxy:2375`` +* Compute device: ``CPU`` +* Network: ``nextcloud-aio`` +* Nextcloud URL (passed to ExApps): ``https://$NC_DOMAIN`` + +Docker Socket Proxy security +**************************** + +AIO Docker Socket Proxy has strictly limited access to the Docker APIs described in `HAProxy configuration `_. + + +NC to ExApp Communication +------------------------- + +Each type of DeployDaemon necessarily implements the ``resolveExAppUrl`` function. + +It has such prototype: + +.. code-block:: php + + public function resolveExAppUrl( + string $appId, string $protocol, string $host, array $deployConfig, int $port, array &$auth + ) {} + +where: + +* **protocol** is daemon protocol value +* **host** is daemon host value, *can be DNS:port or IP:PORT or even path to docker socket*. +* **port** is an integer with ExApp port +* **deployConfig** can be custom for each Daemon type +* **auth** is an optional array, with *Basic Authentication* data if needed to access ExApp + +.. note:: + + Starting with AppAPI version ``2.5.0``, the optional additional parameter *OVERRIDE_APP_HOST* can be used to + override the host that will be used for ExApp binding. + + It can be ``0.0.0.0`` in some specific configurations, when VPN is used + or both Nextcloud instance and ExApps are one the same physical machine but different virtual environments. + + Also you can specify something like ``10.10.2.5`` and in this case ``ExApp`` wil try to bind to that address and + AppAPI will try to send request s directly to this address assuming that ExApp itself bound on it. + +The simplest implementation is in **Manual-Install** deploy type: + +.. code-block:: php + + public function resolveExAppUrl( + string $appId, string $protocol, string $host, array $deployConfig, int $port, array &$auth + ): string { + $auth = []; + if (isset($deployConfig['additional_options']['OVERRIDE_APP_HOST']) && + $deployConfig['additional_options']['OVERRIDE_APP_HOST'] !== '' + ) { + $wideNetworkAddresses = ['0.0.0.0', '127.0.0.1', '::', '::1']; + if (!in_array($deployConfig['additional_options']['OVERRIDE_APP_HOST'], $wideNetworkAddresses)) { + $host = $deployConfig['additional_options']['OVERRIDE_APP_HOST']; + } + } + return sprintf('%s://%s:%s', $protocol, $host, $port); + } + +Here we see that AppAPI send requests to **host**:**port** specified during daemon creation. + +Now let's take a look at the Docker Daemon implementation of ``resolveExAppUrl``: + +.. code-block:: php + + public function resolveExAppUrl( + string $appId, string $protocol, string $host, array $deployConfig, int $port, array &$auth + ): string { + $auth = []; + if (isset($deployConfig['additional_options']['OVERRIDE_APP_HOST']) && + $deployConfig['additional_options']['OVERRIDE_APP_HOST'] !== '' + ) { + $wideNetworkAddresses = ['0.0.0.0', '127.0.0.1', '::', '::1']; + if (!in_array($deployConfig['additional_options']['OVERRIDE_APP_HOST'], $wideNetworkAddresses)) { + return sprintf( + '%s://%s:%s', $protocol, $deployConfig['additional_options']['OVERRIDE_APP_HOST'], $port + ); + } + } + $host = explode(':', $host)[0]; + if ($protocol == 'https') { + $exAppHost = $host; + } elseif (isset($deployConfig['net']) && $deployConfig['net'] === 'host') { + $exAppHost = 'localhost'; + } else { + $exAppHost = $appId; + } + if (isset($deployConfig['haproxy_password']) && $deployConfig['haproxy_password'] !== '') { + $auth = [self::APP_API_HAPROXY_USER, $deployConfig['haproxy_password']]; + } + return sprintf('%s://%s:%s', $protocol, $exAppHost, $port); + } + +Here we have much more complex algorithm of detecting to where requests should be send. + +First of all if protocol is set to ``https`` AppAPI always send requests to daemon host, +and this is in case of ``https`` it is a HaProxy that will forward requests to ExApps that will be listen on ``localhost`` + +Briefly it will look like this(*haproxy_host==daemon host value*): + +NC --> *https* --> ``haproxy_host:ex_app_port`` --> *http* --> ``localhost:ex_app_port`` + +When protocol is not ``https`` but ``http``, then what will be the endpoint where to send requests is determined by ``$deployConfig['net']`` value. + +If ``net`` is defined and equal to ``host`` then AppAPI assumes that ExApp is installed somewhere in the current host network and will be available on ``localhost`` loop-back adapter. + +NC --> *http* --> ``localhost:ex_app_port`` + +In all other cases ExApp should be available by it's name: e.g. when using docker **custom bridge** network all containers available by DNS. + +NC --> *http* --> ``app_container_name:ex_app_port`` + +This three different types of communication covers all most popular configurations. diff --git a/_sources/DevSetup.rst.txt b/_sources/DevSetup.rst.txt new file mode 100644 index 00000000..e147e896 --- /dev/null +++ b/_sources/DevSetup.rst.txt @@ -0,0 +1,59 @@ +.. _dev-setup: + +Setting up dev environment +========================== + +We highly recommend using `Julius Haertl docker setup `_ for the Nextcloud dev setup. + +Suggested IDE: **PhpStorm**, though you can certainly use any IDE of your preference such as **VS Code** or **Vim**. + +Get last version from GitHub +"""""""""""""""""""""""""""" + +Assuming you're in the ``apps`` folder of Nextcloud with command :command:`git`:: + + git clone https://github.com/cloud-py-api/app_api.git + +Change to the ``app_api`` directory with :command:`shell`:: + + cd app_api + +Then, build frontend assets in development mode with :command:`shell`:: + + npm ci && npm run dev + +After this, you can enable it from the directory where the ``occ`` command resides, with :command:`shell`:: + + ./occ app:enable --force app_api + +In Place of a Conclusion +"""""""""""""""""""""""" + +There are several make commands available to ease frequent development actions. + +To see the complete list, execute ``make help``. + +Docker remote API +***************** + +The Docker Engine remote API can be easily configured via ``make dock2port`` command. +The command will create a docker container to provide remote Docker Engine API. + +Afterward, register DaemonConfigs in Nextcloud using ``make dock-port`` command. + +Docker by socket +**************** + +For Docker via socket, use the command ``make dock-sock``. +This registers DaemonConfigs in Nextcloud for the default socket connection (``/var/run/docker.sock``). + +Make sure that socket has enough permissions for Nextcloud and webserver user to access it +and actually forwarded to the container: + +.. code-block:: + + ... + volumes: + ... + - /var/run/docker.sock:/var/run/docker.sock + ... diff --git a/_sources/Installation.rst.txt b/_sources/Installation.rst.txt new file mode 100644 index 00000000..389c58f6 --- /dev/null +++ b/_sources/Installation.rst.txt @@ -0,0 +1,85 @@ +Installation +============ + +There are two ways to install the AppAPI: from the `AppStore `_ or from the source code. + +.. note:: + + AppAPI 3.0.0 is the last version supported Nextcloud 27. + + +Installation from the AppStore +------------------------------ + +Simply navigate to the Apps management page in your Nextcloud and setup the AppAPI from the Tools category. + +Installation from the source code +--------------------------------- + +To install the AppAPI from the source code, follow these steps: + +1. Clone the AppAPI repository into your apps directory +******************************************************* + +Clone the latest main branch: + + .. code-block:: bash + + git clone https://github.com/cloud-py-api/app_api.git + +or clone a specific version by specifying the version tag: + + .. code-block:: bash + + git clone https://github.com/cloud-py-api/app_api.git --branch + +where ```` is the version you want to install. + + +2. Build frontend assets in production mode +******************************************** + + .. code-block:: bash + + npm ci && npm run build + +3. Enable the AppAPI +******************** + + .. code-block:: bash + + ./occ app:enable --force app_api + + +To install it in development mode, follow the instructions on this page: :ref:`dev-setup`. + +4. Setup Deploy daemon +********************** + +Upon the successful installation of the AppAPI, a one-time configuration is essential. +Details on this configuration can be found in the subsequent section: :ref:`create-deploy-daemon`. + +4.1 Deploy daemon configuration +******************************* + +Deploy daemon configuration steps: + +1. Go to the AppAPI admin settings. +2. Click on the "Register Daemon" button. +3. Fill in the required fields: + - ``Name``: unique name of the Deploy daemon + - ``Display name``: the name that will be displayed in the UI + - ``Deployment method``: by default you will need to choose ``docker_install``, ``manual_install`` is for development or custom use case of manual ExApp installation + - ``Daemon Host``: hostname/IP address + port of the Deploy daemon + - ``Nextcloud URL``: autofilled with current domain, you might need to change the protocol to http/https depending on your setup + - ``Set as default daemon``: check if you want set new Deploy daemon as default + - ``Enable https``: check if your Deploy daemon (Docker Socket Proxy) is configured with TLS + - Deploy Config: + - ``Network``: Docker network name, depends on your networking setup, enforces to "host" if "Enable https" is checked + - ``HaProxy password``: password for Docker Socket Proxy, if it is configured with TLS + - ``Compute Device``: CPU, CUDA or ROCm, depending on your hardware config on Deploy daemon host machine + - ``Add additional option`` (see :ref:`additional_options_list`): setup additional KEY + VALUE deploy config options +4. Check connection: to verify configuration is correct +5. Register: to save the Deploy daemon configuration + +Deployment configuration examples can be found :ref:`here `. diff --git a/_sources/ManagingExternalApplications.rst.txt b/_sources/ManagingExternalApplications.rst.txt new file mode 100644 index 00000000..3f11f524 --- /dev/null +++ b/_sources/ManagingExternalApplications.rst.txt @@ -0,0 +1,117 @@ +Managing External Applications +============================== + +There are two ways to manage ExApps: + +1. Using OCC CLI tool +2. Using the ExApp Management UI + + +OCC CLI +^^^^^^^ + +There are several commands to work with ExApps: + +1. Register +2. Unregister +3. Update +4. Enable +5. Disable +6. List ExApps +7. List ExApp users (removed since AppAPI 3.0.0) +8. List ExApp scopes + +Register +-------- + +Command: ``app_api:app:register [--force-scopes] [--info-xml INFO-XML] [--json-info JSON-INFO] [--] `` + +The register command is the first ExApp installation step. + +Arguments +********* + + * ``appid`` - unique name of the ExApp (e.g. ``app_python_skeleton``, must be the same as in deployed container) + * ``daemon-config-name`` - unique name of the daemon (e.g. ``docker_local_sock``) + +Options +******* + + * ``--force-scopes`` *[optional]* - force scopes approval + * ``--json-info JSON-INFO`` **[optional]** - ExApp deploy JSON info (json string) + * ``--info-xml INFO-XML`` **[optional]** - path to info.xml file (url or local absolute path) + + +Unregister +---------- + +Command: ``app_api:app:unregister [--rm-data] [--force] [--silent] [--] `` + +To remove an ExApp you can use the unregister command. +There are additional options to keep the ExApp persistent storage (data volume). + +Arguments +********* + + * ``appid`` - unique name of the ExApp (e.g. ``app_python_skeleton``, must be the same as in deployed container) + +Options +******* + + * ``--rm-data`` *[optional]* - remove ExApp persistent storage (data volume) + * ``--force`` *[optional]* - continue removal even if some error occurs. + * ``--silent`` *[optional]* - print a minimum of information, display only some errors, if any. + +Update +------ + +Command: ``app_api:app:update [--info-xml INFO-XML] [--force-update] [--force-scopes] [-e|--enabled] [--] `` + +ExApp will be updated if there is a new version available. + +Arguments +********* + + * ``appid`` - unique name of the ExApp (e.g. ``app_python_skeleton``, must be the same as in deployed container) + +Options +******* + + * ``--info-xml INFO-XML`` **[optional]** - path to info.xml file (url or local absolute path) + * ``--force-update`` *[optional]* - force ExApp update (do not prompt for confirmation) + * ``--force-scopes`` *[optional]* - force scopes approval (accept all scopes) + * ``-e|--enabled`` *[optional]* - enable ExApp after update + +Enable +------ + +Command: ``app_api:app:enable `` + +Disable +------- + +Command: ``app_api:app:disable `` + +List ExApps +----------- + +Command: ``app_api:app:list`` + +ListExApps command will show all ExApps: + +.. code-block:: + + ExApps: + appid (Display Name): version [enabled/disabled] + to_gif_example (To Gif Example): 1.0.0 [enabled] + upscaler_example (Upscaler Example): 1.0.0 [enabled] + +Using the ExApp Management UI +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +ExApps management is similar to default Apps management. +To access ExApps management navigate using Admin settings dropdown menu or from AppAPI admin settings section. + +.. note:: + + ExApps management support only apps from App Store. For manual-install type use CLI ExApps management commands. diff --git a/_sources/TestDeploy.rst.txt b/_sources/TestDeploy.rst.txt new file mode 100644 index 00000000..8cdb1117 --- /dev/null +++ b/_sources/TestDeploy.rst.txt @@ -0,0 +1,97 @@ +.. _test_deploy: + +Test Deploy Daemon +------------------ + +You can test each Daemon configuration deployment from the AppAPI Admin settings. + +.. image:: ./img/test_deploy.png + + +Status Checks +^^^^^^^^^^^^^ + +The Deploy test installs a `test-deploy `_ ExApp +to verify each step of the deployment process, including a hardware support check - +for each compute device, there is a separate Docker image. + +.. note:: + The Test Deploy ExApp container is not removed after the test as it's needed for logs and status checks. + You can remove it after testing from the External Apps page. + The Docker images are also not removed from the Daemon; you can clean up unused images with the ``docker image prune`` command. + +.. image:: ./img/test_deploy_modal_4.png + + +Register +******** + +The Register step is the first step; it checks if the ExApp is registered in Nextcloud. + +Image Pull +********** + +The Image Pull step downloads the ExApp Docker image. + +Possible errors: + +- Image not found (e.g. not public, no image found for your hardware architecture) +- Image pull failed (e.g., due to network issues) +- Image pull timeout +- Your Docker Socket Proxy is not configured correctly and blocks access to this Docker Engine API + +Container Started +***************** + +The Container Started step verifies that the ExApp container is created and started successfully. + +Possible errors: + +- Container failed to start with GPU support (may be missing or misconfigured) + - For NVIDIA, refer to the `NVIDIA Docker configuration docs `_. + - For AMD, refer to the `ROCm Docker configuration docs `_. +- The ExApp issue during startup (e.g. not enough memory) + + +Heartbeat +********* + +The Heartbeat step checks if the container's health check is finished and the container is healthy. +The ExApp might have additional pre-configuration logic during this step. + +Possible errors: + +- ExApp failed to start a web server, e.g., if the port is already in use (this should be visible in the container logs) +- ExApp heartbeat_count keeps increasing, this may indicate that the ExApp couldn't start properly +- Nextcloud can not reach the ExApp container, e.g., due to a network issue or a firewall + +Init +**** + +The Init step checks if the ExApp is initialized and ready to use. +During the init step, the ExApp may perform downloads of extra stuff required for it. + +Possible errors: + +- Initialization failed (e.g., due to network issues or timeout) + + +Enabled +******* + +The Enabled step checks if the ExApp is enabled and ready to use. +During this step, the ExApp registers all the required and available APIs of the Nextcloud AppFramework. + +Possible errors: + +- ExApp did not respond to the enable request +- ExApp failed to enable due to a failure in registering AppAPI Nextcloud AppFramework APIs (this should be visible both in the container logs and in the Nextcloud logs if there are any errors) + + +Download Logs +^^^^^^^^^^^^^ + +You can download the logs of the last test deploy attempt container. + +.. note:: + Downloading Docker container logs is only possible for containers using the json-file or journald logging drivers. diff --git a/_sources/faq/BehindCompanyProxy.rst.txt b/_sources/faq/BehindCompanyProxy.rst.txt new file mode 100644 index 00000000..fe696e12 --- /dev/null +++ b/_sources/faq/BehindCompanyProxy.rst.txt @@ -0,0 +1,161 @@ +Corporate Proxy - Permanent Settings for PHP CLI +================================================ + +If you're using our application within a corporate network that requires proxy settings, you might encounter issues when running PHP CLI commands that attempt to access the internet. + +To resolve this, you need to configure permanent proxy settings for the PHP CLI environment. + +Symptoms +-------- + +When running the command: + +.. code-block:: bash + + php occ app_api:app:register --force-scopes test-deploy docker_socket_proxy --info-xml https://raw.githubusercontent.com/cloud-py-api/test-deploy/main/appinfo/info.xml --test-deploy-mode --no-ansi --no-warnings + +You may receive an error similar to: + +.. code-block:: text + + file_get_contents(https://raw.githubusercontent.com/cloud-py-api/test-deploy/main/appinfo/info.xml): Failed to open stream: Connection timed out at /var/www/html/custom_apps/app_api/lib/Service/ExAppService.php#277 + +Cause +----- + +This issue occurs because the PHP CLI environment does not have the proxy settings configured, unlike the web PHP environment which may already be using proxy settings specified in your web server configuration. + +Permanent Solution +------------------ + +To permanently configure proxy settings for PHP CLI, you can either modify the PHP CLI `php.ini` file or set environment variables system-wide. + +Method 1: Edit PHP CLI `php.ini` File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. **Locate the PHP CLI `php.ini` File** + + Run the following command to find the loaded configuration file for PHP CLI: + + .. code-block:: bash + + php --ini + + Look for the line: + + .. code-block:: text + + Loaded Configuration File: /path/to/php.ini + +2. **Edit the `php.ini` File** + + Open the `php.ini` file in a text editor with appropriate permissions: + + .. code-block:: bash + + sudo nano /path/to/php.ini + +3. **Add Proxy Settings** + + Add the following lines to configure the proxy settings: + + .. code-block:: ini + + [HTTP] + ; Proxy settings for HTTP + http.proxy_host = "proxy.example.com" + http.proxy_port = 8080 + http.proxy_user = "username" + http.proxy_password = "password" + + [HTTPS] + ; Proxy settings for HTTPS + https.proxy_host = "proxy.example.com" + https.proxy_port = 8080 + https.proxy_user = "username" + https.proxy_password = "password" + + Replace the placeholders with your actual proxy server details: + + - `proxy.example.com`: Your proxy server address. + - `8080`: Your proxy server port. + - `username`: Your proxy username (if required). + - `password`: Your proxy password (if required). + +4. **Save and Close the File** + + Save the changes and exit the text editor. + +5. **Verify the Configuration** + + Run the PHP CLI command again: + + .. code-block:: bash + + php occ app_api:app:register + + It should now be able to access the internet through the proxy. + +**Note:** Not all PHP functions respect the proxy settings in `php.ini`. If issues persist, consider using system-wide environment variables. + +Method 2: Set System-Wide Environment Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. **Edit Shell Profile** + + For a permanent solution, add the proxy settings to the system-wide environment variables. Open the `/etc/environment` file: + + .. code-block:: bash + + sudo nano /etc/environment + +2. **Add Proxy Environment Variables** + + Add the following lines to the file: + + .. code-block:: bash + + http_proxy="http://proxy.example.com:8080" + https_proxy="http://proxy.example.com:8080" + + # If your proxy requires authentication: + http_proxy="http://username:password@proxy.example.com:8080" + https_proxy="http://username:password@proxy.example.com:8080" + + Replace the placeholders with your actual proxy details. + +3. **Apply the Changes** + + Log out and log back in, or reboot the system to apply the changes. + +4. **Verify the Configuration** + + Run the command again: + + .. code-block:: bash + + php occ app_api:app:register --force-scopes test-deploy docker_socket_proxy --info-xml https://raw.githubusercontent.com/cloud-py-api/test-deploy/main/appinfo/info.xml --test-deploy-mode --no-ansi --no-warnings + + It should now work without connectivity issues. + +**Note:** This method sets the proxy settings for all users and applications on the system. + +Troubleshooting +--------------- + +- **Incorrect Proxy Details** + + Ensure all proxy details are correct. Incorrect hostnames, ports, or credentials will prevent connectivity. + +- **Environment Variables Not Loaded** + + Make sure the environment variables are correctly loaded. A system reboot or re-login may be necessary. + +- **Firewall Restrictions** + + Verify with your network administrator that your system is allowed to access the internet through the proxy. + +Contact Support +--------------- + +If you've followed these steps and still experience issues, please contact our support team for further assistance. diff --git a/_sources/faq/DockerContainerRegistry.rst.txt b/_sources/faq/DockerContainerRegistry.rst.txt new file mode 100644 index 00000000..ef77a512 --- /dev/null +++ b/_sources/faq/DockerContainerRegistry.rst.txt @@ -0,0 +1,8 @@ +Docker Container Registry +========================= + +How to use a private Docker container registry with authentication? +******************************************************************* + +Currently, we don't support the authentication for Docker container registry. +The ExApp images must be publicly available. diff --git a/_sources/faq/DockerSocketProxy.rst.txt b/_sources/faq/DockerSocketProxy.rst.txt new file mode 100644 index 00000000..3df99427 --- /dev/null +++ b/_sources/faq/DockerSocketProxy.rst.txt @@ -0,0 +1,56 @@ +Docker Socket Proxy +=================== + +The recommended way to setup AppAPI Deploy daemon +is to use our `Docker Socket Proxy implementation `_. + +Nextcloud AppAPI DSP +-------------------- + +Nextcloud AppAPI DSP (Docker Socket Proxy) - is a simple Docker container that provides a secure way to access the Docker Engine API and ExApps. +It is secured with haproxy Basic authentication. +There are two parts of reverse proxy configuration: + +- HaProxy config for `Docker Engine API `_ +- HaProxy config for `ExApps `_ + +.. note:: + + For remote Docker Socket Proxy setup, it should expose the ports on the host. + + +.. _faq_nextcloud-aio-docker-socket-proxy: + +Nextcloud AIO +------------- + +Nextcloud AIO implements `Docker Socket Proxy container `_ which automatically setting up, +you just need to tick the checkbox in AIO configuration interface to enable it. +AppAPI automatically creates the default Deploy daemon configuration in Nextcloud AIO. + +See :ref:`nextcloud-in-docker-aio-all-in-one` for more details. + +.. note:: + + Nextcloud AIO is not limited to its default Deploy daemon. + You can setup any other Deploy daemon (local or remote) to use it in AppAPI. + + +Other implementations +--------------------- + +Our implementation is inspired by `Tecnativa Docker Socket Proxy `_, +by default, it restricts access to the required by AppAPI Docker Engine APIs. +In this case, you will have to enable these APIs via the environment variables: + +- ``IMAGES=1`` +- ``CONTAINER=1`` +- ``POST=1`` + +.. note:: + + For local Deploy daemon setup other implementations of Docker Socket Proxy may be enough. + But for remote Deploy daemon setup, we recommend using our DSP, + as `we allow `_ only the Docker Engine APIs we actually use in AppAPI, + and it is additionally secured with haproxy authentication. + diff --git a/_sources/faq/GpuSupport.rst.txt b/_sources/faq/GpuSupport.rst.txt new file mode 100644 index 00000000..3c350ddb --- /dev/null +++ b/_sources/faq/GpuSupport.rst.txt @@ -0,0 +1,28 @@ +GPU support +----------- + +How to enable GPU support for the Deploy daemon? +************************************************ + +To enable GPU support, you have to specify the GPU compute device when registering the Deploy daemon configuration. + +In this way, by default, AppAPI will create ExApp containers with request to the Docker Engine to attach all available GPU devices. +This also involves the specific ExApp supporting work with GPU internally +and the necessary Docker runtime toolkits installed on the Deploy daemon host: + +- For NVIDIA, refer to the `NVIDIA Docker configuration docs `_. +- For AMD, refer to the `ROCm Docker configuration docs `_. + +.. note:: + + If you encounter any issues with GPU support, it is highly dependent on the specific GPU device, + software libraries and therefore ExApps support of different hardware, or other factors. + Please, feel free to ask for help by creating an issue. + + +How to limit the number of GPUs per ExApp? +****************************************** + +Currently, there is no such configuration option. +AppAPI attaches all available GPU devices to each ExApp container on the same Deploy daemon. + diff --git a/_sources/faq/Scaling.rst.txt b/_sources/faq/Scaling.rst.txt new file mode 100644 index 00000000..79c1325c --- /dev/null +++ b/_sources/faq/Scaling.rst.txt @@ -0,0 +1,22 @@ +Scaling +======= + +AppAPI delegates the scaling task to the ExApps itself. +This means, that the ExApp must be designed in a way to be able to scale vertically. +As for the horizontal scaling, currently, it is not possible. +Except, for example, the Server-Workers architecture, is a good way to support basic scaling capabilities, +where the Server is your ExApp and the Workers are the external machines that can work with the ExApp +using Nextcloud user authentication. +Aadditional clients (or workers) can be (optionally) added (or attached) to the ExApp +to increase the capacity and performance. + + +GPUs scaling +------------ + +Currently, if Deploy daemon configured with GPUs available, +AppAPI by default will attach all available GPU devices to each ExApp container on this Deploy daemon. +This means, that these GPUs are shared between all ExApps on the same Deploy daemon. +Therefore, for the ExApps that require GPU and uses it heavily, +it is recommended to have a separate Deploy daemon (host) for them. + diff --git a/_sources/faq/Troubleshooting.rst.txt b/_sources/faq/Troubleshooting.rst.txt new file mode 100644 index 00000000..826e6a9d --- /dev/null +++ b/_sources/faq/Troubleshooting.rst.txt @@ -0,0 +1,53 @@ +Troubleshooting +=============== + +This section describes common steps to troubleshoot specific issues. + + +How to troubleshoot networking issues? +-------------------------------------- + +Networking issues can be not so straightforward to identify and resolve. +Here are some common steps to verify the network configuration: + +- Verify that the Deploy daemon is running and accessible (AppAPI admin settings - Select Deploy daemon - Check connection). +- Verify the network mode and access levels, firewall, vpn, etc. +- Verify that Nextcloud is reachable from the Deploy daemon host +- Verify that the Deploy daemon host is reachable from the Nextcloud host +- Verify that Nextcloud is reachable from the ExApp container +- Verify if there is no DNS resolution issues +- Verify is there is no SSL certificate issues +- If there are HTTP 401 Unauthorized errors, check the ExApp (``docker logs nc_app_``) / Nextcloud logs, on which API route it fails to authenticate, try to re-enable AppAPI and re-install the ExApp. + +.. note:: + If your case is not documented here, or doesn't exists on GitHub issues, + please feel free to ask it by creating an issue in the `AppAPI repository `_. + + +ExApp deployment issues +----------------------- + +The deployment issues questions are covered in the :ref:`Test Deploy ` section. +Generally speaking, there are three steps to find the proper error message to understand the problem: + +1. Check Nextcloud logs +2. Check ExApp container logs (available only if ExApp container is created and/or running) +3. Check Deploy daemon host logs (``journalctl -u docker.service``) +4. Check Docker Socket Proxy logs (if used, and if needed, e.g. for SSL or 401 errors check) + + +Failed to create volume +----------------------- + +If you encounter "Failed to create volume" error, please check the following: + +- Make sure that there is enough disk space on the host machine. +- Check the Docker system logs while reproducing the issue (``journalctl -u docker.service``). + + +ExApps management list of apps from AppStore is empty +----------------------------------------------------- + +This issue may occur if you are loading the ExApps management (or regular Apps management) page +frequently during the short period of time and therefore your IP can be blocked by the AppStore rate-limits protection. +Please, wait for a while and try again. diff --git a/_sources/faq/index.rst.txt b/_sources/faq/index.rst.txt new file mode 100644 index 00000000..49629b54 --- /dev/null +++ b/_sources/faq/index.rst.txt @@ -0,0 +1,24 @@ +Frequently Asked Questions +========================== + +This section contains the most common or problematic questions +that users or developers may encounter when working with AppAPI. +It can be used as pointer to another parts of the documentation, as it usually refers to, +or provide a brief answer. + +.. note:: + + This section will be updated with time, as new questions arise. + If you have a question that is not listed here or the answer is not enough for you, please feel free to + ask it by creating an issue in the `AppAPI repository `_. + + +.. toctree:: + :maxdepth: 2 + + DockerContainerRegistry + DockerSocketProxy + GpuSupport + Scaling + BehindCompanyProxy + Troubleshooting diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..0295c80d --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,35 @@ +AppAPI documentation +============================ + +The AppAPI is a project introduced by Nextcloud to revolutionize the process of application development within the Nextcloud ecosystem. + +Overview +======== + +The main tasks of the ecosystem are: + + * Providing a reliable and fast method for authenticating applications + * Supporting various application deployment options + * Offering a clear and straightforward application administration interface + * Ensuring a reliable implementation of all the necessary missing APIs for applications + * Supplying clear, understandable documentation and support on how to implement libraries in other programming languages for writing next-gen applications for Nextcloud + +If you have any questions or corrections regarding the documentation, +we would be glad to address them in discussions, incorporate corrections through pull requests, +and handle complex problems through issues. + +================== + +.. toctree:: + :maxdepth: 1 + + Installation + DeployConfigurations + CreationOfDeployDaemon + TestDeploy + ManagingExternalApplications + Concepts + tech_details/index.rst + DevSetup + notes_for_developers/index.rst + faq/index.rst diff --git a/_sources/notes_for_developers/DevelopmentEnvironment.rst.txt b/_sources/notes_for_developers/DevelopmentEnvironment.rst.txt new file mode 100644 index 00000000..77f2884b --- /dev/null +++ b/_sources/notes_for_developers/DevelopmentEnvironment.rst.txt @@ -0,0 +1,25 @@ +.. _DevelopmentEnvironment: + +Development environment +======================= + +The development environment for AppAPI first of all requires a default Nextcloud dev setup. +You can find more information on that in the `Nextcloud development environment docs `_. +The AppAPI dev setup steps listed :ref:`here `. + + +Deploy daemons types +-------------------- + +There are two types of Deploy daemons that can be used for development and testing of ExApp: + +1. ``manual_install``: This type of Deploy daemon is running manually in the host machine. + You can create it in AppAPI admin settings using template. + This is useful for development of ExApp, when you run your ExApp manually in the host. +2. ``docker_install``: This type of Deploy daemon is running in a Docker container. + +Docker Socket Proxy +------------------- + +For development and testing locally, the simplest is to use the `Nextcloud AppAPI DSP HTTP `_. + diff --git a/_sources/notes_for_developers/ExAppDevelopmentSteps.rst.txt b/_sources/notes_for_developers/ExAppDevelopmentSteps.rst.txt new file mode 100644 index 00000000..1d4e4b72 --- /dev/null +++ b/_sources/notes_for_developers/ExAppDevelopmentSteps.rst.txt @@ -0,0 +1,124 @@ +.. _ExAppDevelopment: + +ExApp development +================= + +ExApp development process is similar to the development of the regular Nextcloud PHP app, +and should follow the same guidelines in terms of security, design and coding style (`ref `_) +based on your programming language standards. + +Despite the fact, that ExApp can be developed in any language, it's still recommended to have the understanding +of the Nextcloud `PHP request life cycle `_ and other basic concepts, +as they are usually similar to the ExApp backend to which Nextcloud communicates. + +You can think about ExApp like a microservice (Docker container) +that runs separately from Nextcloud on the Deploy daemon, which can be remote or local. +The communication between Nextcloud and ExApp is done via network secured with AppAPI authentication (:ref:`app_api_auth`). + +Lets go through the ExApp development steps briefly. + +0. Setting up the development environment +----------------------------------------- + +First of all, you need to have a Nextcloud dev setup, refer to :ref:`DevelopmentEnvironment` for more details. + +1. Starting from template +------------------------- + +Next step, is to setup the ExApp skeleton. +There are several ExApp examples available so you can have a look at them and start from. +The ExApp template and examples: + + - ``[Python]`` `App Skeleton `_ + - ``[Python]`` `UI Example Skeleton `_ + - ``[Python]`` `More complex ExApp UI example with 3rdparty service `_ + - ``[GoLang]`` `Go Lang ExApp example `_ + - etc. + +They contain the basic structure of the ExApp, including: + +- Dockerfile +- ExApp backend +- ExApp frontend +- Manual translations tools setup and example script to convert translation files to Nextcloud l10n format and for your programming language +- Some necessary GitHub workflows (e.g. `Docker image build workflows `_) + +More details are available in the :ref:`ExAppOverview` section. + + +3. Development +-------------- + +The development process basically contains from the following steps: + +- Implement the ExApp <-> Nextcloud :ref:`lifecycle methods `: + #. ``/heartbeat``: ExApp heartbeat method. + #. ``/init``: ExApp initialization method. + #. ``/enabled``: ExApp enable/disable method. +- Implement the ExApp backend API and logic. +- Implement the ExApp frontend (Nextcloud Vue.js app) [optional]. + + +4. Packaging +------------ + +The ExApp packaging can be done manually, or via GitHub actions. +It is recommended to use GitHub actions for packaging, +as it will automate the process of building the Docker image and pushing it to the Docker registry. +The GitHub action workflow for building Docker images can be found in the `3rdparty service example `_. + +4.1 Hardware acceleration +************************* + +If your ExApp work with GPUs, you should consider building different Docker images for each compute device. +Currently, there are 3 main compute devices to target with custom Docker images: + +- CPU (default, no specific tag) +- GPU: CUDA (NVIDIA) (``:-cuda``) +- GPU: ROCm (AMD) (``:-rocm``) + +.. note:: + + If the Deploy daemon configured with the GPU compute device, + AppAPI will try to pull the Docker image with the GPU support first (``:-``, `ref PR `_). + If the image is not found, AppAPI will try to pull the base (CPU) image (``:``). + + +Dockerfile +********** + +The Dockerfile is required to build the Docker image for the ExApp. +The guideline for writing the Dockerfile can be found in the `Dockerfile best practices `_. + +Short recommendations: + +1. Keep the Dockerfile as small as possible. +2. Use minimal base images to keep the image size small. +3. Place rarely changing instructions at the top of the Dockerfile to take advantage of Docker's caching mechanism. + + +Logging +******* + +The logs in Docker container are shown from the standard output and error streams. +To be able admin to see the important logs from ExApp container, +you should consider redirecting the logs to the standard output and standard error streams. +More info in `official docs for logging `_. + + +5. AppStore publishing +---------------------- + +Once the ExApp is ready, and the Docker image is available in the Docker registry, +you can follow `the AppStore publishing process `_. +It's the same as for the regular Nextcloud app, but with the requirement of :ref:`the ExApp specific fields ` in the ``appinfo/info.xml`` file. + + +6. Testing +---------- + +It is important to ensure that your ExApp works as expected. +We recommend to have different types of dev setup configuration to test all of them. +While the main development is done locally via ``manual_install``, you must also ensure that +the ExApp works correctly in Docker container with http and https Docker Socket proxy. + diff --git a/_sources/notes_for_developers/ExAppLifecycle.rst.txt b/_sources/notes_for_developers/ExAppLifecycle.rst.txt new file mode 100644 index 00000000..b4aa7970 --- /dev/null +++ b/_sources/notes_for_developers/ExAppLifecycle.rst.txt @@ -0,0 +1,155 @@ +.. _ex_app_lifecycle: + +ExApp lifecycle +=============== + +The ExApp lifecycle is a set of communication rules (or protocol) between Nextcloud and ExApp. +They are required as for the microservice architecture of ExApp. +This section is an overview, more details on that here: :ref:`app_installation_flow`, :ref:`app_deployment`. + + +.. _ex_app_lifecycle_methods: + + +ExApp lifecycle methods +----------------------- + +When ExApp is being installed in Nextcloud, there are several lifecycle steps happening. +The ExApp lifecycle requires the following API endpoint handlers (order is preserved): + + - 0. ``healthcheck``: Docker container healthcheck. + - 1. ``/heartbeat``: **[required]** ExApp heartbeat handler. + - 2. ``/init``: **[optional]** ExApp initialization handler. + - 3. ``/enabled``: **[required]** ExApp enable/disable handler. + + +Healthcheck +*********** + +Docker allows you to define a custom healthcheck script for your container (specified in the Dockerfile). +You can define here if needed custom container startup logic. + +.. note:: + + AppAPI healthcheck timeout is 15 minutes. + + +Heartbeat +********* + +The ``GET /heartbeat`` method is called periodically by Nextcloud to check the ExApp health status, +if its webserver is running and recieving requests. + +URL: ``GET http://localhost:2345/heartbeat`` + +AppAPI expects a response with HTTP status 200. +This step fails if the ExApp does not respond within 10 minutes. + +.. note:: + + This endpoint should be available **without AppAPIAuth authentication**. + There is a 10 minutes timeout for the ExApp to startup and respond to the ``/heartbeat`` request. + + +.. _ex_app_lifecycle_init: + + +Init +**** + +The ``POST /init`` endpoint is called after the ExApp is enabled in Nextcloud. +This is a trigger for ExApp to start its initialization process, e.g. downloading models, starter data, etc. + +.. note:: + + Default init timeout (``init_timeout``) is 40 minutes. It can be changed by admin in AppAPI settings + or via CLI command ``occ config:app:set app_api init_timeout --value 40 --type mixed``. + +URL: ``POST http://localhost:2345/init`` + +AppAPI expects a response with HTTP status 200. + +.. note:: + + If ExApp doesn't not implement ``/init`` endpoint and AppAPI receives ``HTTP 501 NOT IMPLEMENTED`` or ``HTTP 404 NOT FOUND`` response, + AppAPI enables the ExApp. + +The ExApp should update the init progress via the ``PUT /ocs/v2.php/apps/app_api/ex-app/status`` API request, +with ``{ "progress": }`` payload. + + +Enabled +******* + +The ``PUT /enabled?enabled=1|0`` method is called when the ExApp is enabled or disabled in Nextcloud. +The ``enabled`` query parameter is used to determine the ExApp state: 1 - enabled, 0 - disabled. + + +- ``PUT http://localhost:2345/enabled?enabled=1`` - enable ExApp, during this call ExApp should register all needed APIs +- ``PUT http://localhost:2345/enabled?enabled=0`` - disable ExApp, during this call ExApp should unregister all APIs + +AppAPI expects a response with HTTP status 200. Any other status code will be considered as an error. + +.. note:: + + AppAPI timeout for the ``enabled`` handler is 30 seconds. + + +ExApp lifecycle scheme +---------------------- + +Let's review a simple ExApp lifecycle scheme: + + +.. mermaid:: + + sequenceDiagram + participant Nextcloud + participant ExApp + loop Heartbeat + Nextcloud->>ExApp: HTTP GET /heartbeat + ExApp-->>Nextcloud: /heartbeat HTTP 200 + end + Nextcloud->>ExApp: HTTP POST /init + ExApp->>Nextcloud: /init HTTP OK 200 + loop Init + ExApp->>ExApp: Download models, starter data, etc. + ExApp-->>Nextcloud: PUT /ocs/v2.php/apps/app_api/ex-app/status { "progress": 50 } + end + Nextcloud->>ExApp: HTTP PUT /enabled?enabled=1 + ExApp-->>Nextcloud: Register all needed APIs via OCS API + ExApp->>Nextcloud: /enabled HTTP 200 + Nextcloud->>ExApp: HTTP PUT /enabled?enabled=0 + ExApp-->>Nextcloud: Unregister all APIs via OCS API + ExApp->>Nextcloud: /enabled HTTP 200 + + +Nextcloud-side ExApp lifecycle methods +-------------------------------------- + +The Nextcloud-side ExApp lifecycle methods are the OCS APIs. +You can find available AppAPI Nextcloud OCS APIs :ref:`here `. + +.. note:: + + ExApp should register all needed APIs during the ``enabled`` method call. + E.g. UI (:ref:`top-menu `, :ref:`filesactionmenu `), :ref:`occ commands `, etc. + + +AppAPI Authentication +--------------------- + +Nextcloud requests to the ExApp are secured with :ref:`AppAPIAuth `. +The ExApp should validate the authentication using the same algorithm as AppAPI does. + +.. note:: + + Is it up to the developer to apply the rate limits, bruteforce protection, and other security measures + to the ExApp API endpoints. + + +Cookies +******* + +Along with the AppAPIAuth, ExApp can utilize the Nextcloud cookies of the authenticated user, +who made the request to the ExApp. diff --git a/_sources/notes_for_developers/ExAppOverview.rst.txt b/_sources/notes_for_developers/ExAppOverview.rst.txt new file mode 100644 index 00000000..f67bfc9d --- /dev/null +++ b/_sources/notes_for_developers/ExAppOverview.rst.txt @@ -0,0 +1,335 @@ +.. _ExAppOverview: + +ExApp overview +============== + +Basic concept of the AppAPI is to provide a way to develop an app for Nextcloud using any language. +In this way, the ExApp can be written in any language, in particular, the backend part. +Frontend is kept the same. You can think about ExApp as a microservice. + + +ExApp structure +--------------- + +The typical ExApp folder structure is the following: + +- **appinfo/**: contains the ExApp metadata, which is based on the regular `Nextcloud PHP appinfo `_, + but with additional new fields under the ``external-app`` key. +- **ex_app/**: the ExApp specific folder, which contains the ExApp frontend and backend parts. + - **lib/**: contains the ExApp backend part, which can be written in any language. + - **src/**: contains the ExApp frontend part, which is a regular Nextcloud Vue.js application, with small adjustments to the script loading paths (see :ref:`ExApp specific frontend changes `). + - **img/**: contains the ExApp images. + - **css/**: contains the ExApp CSS files. +- **l10n**: contains the :ref:`ExApp translations `. +- **translationfiles**: contains the source translation (``.po``, ``.mo``) files for the ExApp. +- **other folders or files**: depends on your case, e.g. screenshots, scripts, etc. + + +.. _ex_app_info_xml_metadata: + +ExApp metadata +************** + +The ```` info.xml tag is required for the ExApp metadata. +It should contain the following fields: + +.. code-block:: + + + + ghcr.io + cloud-py-api/skeleton + latest + + // deprecated and removed since AppAPI 3.2.0 + FILES + AI_PROVIDERS + ... + + false // deprecated since AppAPI 3.0.0 + + +- **docker-install**: contains the Docker image information for the ExApp. + - **registry**: the Docker registry where the image is stored. + - **image**: the Docker image name. + - **image-tag**: the Docker image tag (version tag). +- **scopes**: the list of the Nextcloud scopes that the ExApp requires (see :ref:`list of scopes `). +- **system**: (deprecated since AppAPI 3.0.0) a boolean value that indicates whether the ExApp is a system app or not. + + +Backend +------- + +The ExApp backend part can be implemented in any language and framework you want, +the only requirement here is to follow the microservice architecture and ExApp <-> Nextcloud :ref:`communication flow `. + +.. note:: + + There is a limitation of AppAPI ExApp proxy - the websocket connections are not supported. + +Each ExApp container have the environment variables set by AppAPI, you can find more about it :ref:`here `. + +Persistent storage +****************** + +For each ExApp, AppAPI creates a Docker volume (``nc_app__data``), that is attached to the ExApp container as a persistent storage. +It is available inside container under the ``/nc_app__data`` path or via ``APP_PERSISTENT_STORAGE`` env passed by AppAPI. + + +.. _ex_app_specific_frontend_changes: + +Frontend +-------- + +The ExApp frontend part is loaded only for :ref:`TopMenu entry `. +It is a regular Nextcloud Vue.js application with a small routing adjustment to the paths, +as they are being loaded via AppAPI proxy from the ExApp server. + +To simplify the usage, we declare a few constants: + +.. code-block:: + + export const EX_APP_ID = 'ui_example' + export const EX_APP_MENU_ENTRY_NAME = 'first_menu' + export const APP_API_PROXY_URL_PREFIX = '/apps/app_api/proxy' + export const APP_API_ROUTER_BASE = '/apps/app_api/embedded' + +The bootstrap of the Vue app (`UI Example boostrap `_) is changes as follows: + +.. code-block:: + + import Vue from 'vue' + import { translate, translatePlural } from '@nextcloud/l10n' + import { generateUrl } from '@nextcloud/router' + import { APP_API_PROXY_URL_PREFIX, EX_APP_ID } from './constants/AppAPI.js' + import { getRequestToken } from '@nextcloud/auth' + + Vue.prototype.t = translate + Vue.prototype.n = translatePlural + Vue.prototype.OC = window.OC + Vue.prototype.OCA = window.OCA + + __webpack_public_path__ = generateUrl(`${APP_API_PROXY_URL_PREFIX}/${EX_APP_ID}/js/`) // eslint-disable-line + __webpack_nonce__ = btoa(getRequestToken()) // eslint-disable-line + + +Frontend routing +**************** + +The frontend routing base URL is also adjusted to be loaded via AppAPI proxy. +For example, the vuex router has the following base URL configuration: + +.. code-block:: + + ... + const router = new VueRouter({ + mode: 'history', + base: generateUrl(`${APP_API_ROUTER_BASE}/${EX_APP_ID}/${EX_APP_MENU_ENTRY_NAME}`, ''), // setting base to AppAPI embedded URL + linkActiveClass: 'active', + ... + +The same applies to the frontend API requests to the ExApp backend API: + +.. code-block:: + + ... + axios.get(generateUrl(`${APP_API_PROXY_URL_PREFIX}/${EX_APP_ID}/some_api_endpoint`)) + ... + + +.. _ex_app_translations: + +L10n translations +----------------- + +Currently, only `manual translations `_ are supported. +To add support of your programming language from translations string extraction using Nextcloud translation tool, +you just need to add your file extensions to it `in createPotFile `_ +and down below adjust the ``--language`` and ``keyword`` parameters. +Our examples using translationtool adjusted in the same way: + +.. code-block:: + + diff --git a/translations/translationtool/src/translationtool.php b/translations/translationtool/src/translationtool.php + index 42513563..8aa06618 100644 + --- a/translations/translationtool/src/translationtool.php + +++ b/translations/translationtool/src/translationtool.php + @@ -67,7 +67,7 @@ public function createPotFile() { + $this->createFakeFileForVueFiles(); + $this->createFakeFileForLocale(); + $translatableFiles = $this->findTranslatableFiles( + - ['.php', '.js', '.jsx', '.mjs', '.html', '.ts', '.tsx'], + + ['.php', '.js', '.jsx', '.mjs', '.html', '.ts', '.tsx', '.py'], + ['.min.js'] + ); + + @@ -79,6 +79,8 @@ public function createPotFile() { + $keywords = ''; + if (substr($entry, -4) === '.php') { + $keywords = '--keyword=t --keyword=n:1,2'; + + } else if (substr($entry, -3) === '.py') { + + $keywords = '--keyword=_ --keyword=_n:1,2'; + } else { + $keywords = '--keyword=t:2 --keyword=n:2,3'; + } + @@ -86,6 +88,8 @@ public function createPotFile() { + $language = '--language='; + if (substr($entry, -4) === '.php') { + $language .= 'PHP'; + + } else if (substr($entry, -3) === '.py') { + + $language .= 'Python'; + } else { + $language .= 'Javascript'; + } + +where we declaring the methods used in source code for translating strings. + +The ExApp translations are stored in the ``l10n`` folder in the ExApp root folder. +For Nextcloud side it still has to contain the files as for regular Nextcloud apps (.js and .json). +There ExApp translation files are copied to the Nextcloud server during installation (removed on uninstall), +and can be used to translate ExApp string on backend or frontend parts the same way as for PHP apps. + +.. note:: + + For the clustered Nextcloud setup, the ExApp translations must be also copied to the other Nextcloud instances, + if the apps folder is not shared between the instances. + It is done automatically only for the instance, where the installation is performed. + + +You might need to convert the translation files to the format that is used in your language. +And this can be done with simple bash script, as `in our example for Python `_: + + +.. code-block:: + + #!/bin/bash + + # This script is used to transform default translation files folders (translationfiles//*.(po|mo)) + # to the locale folder (locale//LC_MESSAGES/*.(po|mo)) + + cd .. + + # Remove the locale/* if it exists to cleanup the old translations + if [ -d "locale" ]; then + rm -rf locale/* + fi + + # Create the locale folder if it doesn't exist + if [ ! -d "locale" ]; then + mkdir locale + fi + + # Loop through the translation folders and copy the files to the locale folder + # Skip the templates folder + + for lang in translationfiles/*; do + if [ -d "$lang" ]; then + lang=$(basename $lang) + if [ "$lang" != "templates" ]; then + if [ ! -d "locale/$lang/LC_MESSAGES" ]; then + mkdir -p locale/$lang/LC_MESSAGES + fi + # Echo the language being copied + echo "Copying $lang locale" + cp translationfiles/$lang/*.po locale/$lang/LC_MESSAGES/ + cp translationfiles/$lang/*.mo locale/$lang/LC_MESSAGES/ + fi + fi + done + + + +Makefile +-------- + +It is recommended to follow our Makefile example with the default set of commands: + +.. note:: + + Makefile is written to work in the `nextcloud-docker-dev `_ dev setup. + +- ``help``: shows the list of available commands. +- ``build-push-cpu``: builds the Docker image for CPU and uploads it to the Docker registry. +- ``build-push-cuda``: builds the Docker image for CUDA and uploads it to the Docker registry. +- ``build-push-rocm``: builds the Docker image for ROCm and uploads it to the Docker registry. +- ``run``: installs the ExApp for Nextcloud latest via the ``occ app_api:app:register`` command, like from UI. +- ``register``: performs registration of running manually ExApp using the ``manual_install`` Deploy daemon. +- ``translation_templates``: execute translationtool.phar to extract translation strings from sources (frontend and backend). +- ``convert_translations_nc``: converts translations to Nextcloud format files (json, js). +- ``convert_to_locale``: copies translations to the common locale//LC_MESSAGES/.(po|mo). Depending on the language, you might need to adjust the script. + + +Example +******* + +Here is an example of regular ExApp Makefile: + +.. code-block:: + + .DEFAULT_GOAL := help + + .PHONY: help + help: + @echo "Welcome to Nextcloud Visionatrix. Please use \`make \` where is one of" + @echo " " + @echo " Next commands are only for dev environment with nextcloud-docker-dev!" + @echo " They should run from the host you are developing on(with activated venv) and not in the container with Nextcloud!" + @echo " " + @echo " build-push-cpu build image for CPU and upload to ghcr.io" + @echo " build-push-cuda build image for CUDA and upload to ghcr.io" + @echo " build-push-rocm build image for ROCm and upload to ghcr.io" + @echo " " + @echo " run install Visionatrix for Nextcloud Last" + @echo " " + @echo " For development of this example use PyCharm run configurations. Development is always set for last Nextcloud." + @echo " First run original 'Visionatrix', then run this Visionatrix and then 'make registerXX', after that you can use/debug/develop it and easy test." + @echo " Do not forget to change paths in 'proxy_requests' function to point to correct files for the frontend" + @echo " " + @echo " register perform registration of running Visionatrix-es into the 'manual_install' deploy daemon." + @echo " " + @echo " L10N (for manual translation):" + @echo " translation_templates extract translation strings from sources" + @echo " convert_translations_nc convert translations to Nextcloud format files (json, js)" + @echo " convert_to_locale copy translations to the common locale//LC_MESSAGES/.(po|mo)" + + .PHONY: build-push-cpu + build-push-cpu: + docker login ghcr.io + docker buildx build --push --platform linux/arm64/v8,linux/amd64 --tag ghcr.io/cloud-py-api/visionatrix:$$(xmlstarlet sel -t -v "//image-tag" appinfo/info.xml) --build-arg BUILD_TYPE=cpu . + + .PHONY: build-push-cuda + build-push-cuda: + docker login ghcr.io + docker buildx build --push --platform linux/amd64 --tag ghcr.io/cloud-py-api/visionatrix-cuda:$$(xmlstarlet sel -t -v "//image-tag" appinfo/info.xml) --build-arg BUILD_TYPE=cuda . + + .PHONY: build-push-rocm + build-push-rocm: + docker login ghcr.io + docker buildx build --push --platform linux/amd64 --tag ghcr.io/cloud-py-api/visionatrix-rocm:$$(xmlstarlet sel -t -v "//image-tag" appinfo/info.xml) --build-arg BUILD_TYPE=rocm . + + .PHONY: run + run: + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister visionatrix --silent --force || true + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:register visionatrix --force-scopes \ + --info-xml https://raw.githubusercontent.com/cloud-py-api/visionatrix/main/appinfo/info.xml + + .PHONY: register + register: + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister visionatrix --silent --force || true + docker exec master-nextcloud-1 rm -rf /tmp/vix_l10n && docker cp l10n master-nextcloud-1:/tmp/vix_l10n + docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:register visionatrix manual_install --json-info \ + "{\"id\":\"visionatrix\",\"name\":\"Visionatrix\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"port\":9100,\"scopes\":[\"AI_PROVIDERS\", \"FILES\", \"USER_INFO\"], \"translations_folder\":\"\/tmp\/vix_l10n\"}" \ + --force-scopes --wait-finish + + .PHONY: translation_templates + translation_templates: + ./translationtool.phar create-pot-files + + .PHONY: convert_translations_nc + convert_translations_nc: + ./translationtool.phar convert-po-files + + .PHONY: convert_to_locale + convert_to_locale: + ./scripts/convert_to_locale.sh + diff --git a/_sources/notes_for_developers/index.rst.txt b/_sources/notes_for_developers/index.rst.txt new file mode 100644 index 00000000..f54b753c --- /dev/null +++ b/_sources/notes_for_developers/index.rst.txt @@ -0,0 +1,17 @@ +.. _notes_for_developers: + +Notes for Developers +==================== + +This section contains the most common information for ExApp developers, +grouped from other parts of documentation. + + +.. toctree:: + :maxdepth: 2 + + DevelopmentEnvironment + ExAppDevelopmentSteps + ExAppOverview + ExAppLifecycle + diff --git a/_sources/tech_details/ApiScopes.rst.txt b/_sources/tech_details/ApiScopes.rst.txt new file mode 100644 index 00000000..0ca1b7c3 --- /dev/null +++ b/_sources/tech_details/ApiScopes.rst.txt @@ -0,0 +1,50 @@ +.. _api_scopes: + +Api Scopes +========== + +.. warning:: + + ApiScopes are deprecated and removed since AppAPI 3.2.0. + +AppAPI design's focus on simplicity and necessity highlights the benefits of defining API scopes. +which simplify the development and integration of applications into the Nextcloud ecosystem. + +With the elimination of optional API scopes, the configuration process during installation becomes more streamlined. +Now, the Nextcloud administrator only needs to focus on the essential API groups, +making the application's setup and permissions handling more efficient and less prone to errors. + +The **info.xml** file continues to play a crucial role, listing all the `required` API groups an +application needs to function correctly. +This not only simplifies version updates, allowing for the seamless introduction of +new API groups or the discontinuation of obsolete ones, but also enhances the overall security and reliability +of the application by ensuring it only has access to necessary functionalities. + +Supported API Groups include: + +* ``2`` SYSTEM +* ``10`` FILES +* ``11`` FILES_SHARING +* ``30`` USER_INFO +* ``31`` USER_STATUS +* ``32`` NOTIFICATIONS +* ``33`` WEATHER_STATUS +* ``50`` TALK +* ``60`` TALK_BOT +* ``61`` AI_PROVIDERS +* ``62`` EVENTS_LISTENER +* ``63`` OCC_COMMAND +* ``110`` ACTIVITIES +* ``120`` NOTES +* ``200`` TEXT_PROCESSING +* ``210`` MACHINE_TRANSLATION +* ``9999`` ALL + +These groups, represented by intuitive names, ensure that applications have +tailored access to the functionalities they need, enhancing performance and user experience. +As Nextcloud evolves, this list of API groups will continue to grow, offering developers a wide array of tools +to create innovative and efficient applications. + +The streamlined approach to API scopes not only simplifies the application development process +but also aligns with best practices in software design, emphasizing clarity, security, and efficiency. +This refinement in the handling of API scopes reflects Nextcloud's commitment to providing a robust and developer-friendly platform. diff --git a/_sources/tech_details/Authentication.rst.txt b/_sources/tech_details/Authentication.rst.txt new file mode 100644 index 00000000..0d07e022 --- /dev/null +++ b/_sources/tech_details/Authentication.rst.txt @@ -0,0 +1,91 @@ +.. _app_api_auth: + +Authentication +============== + +AppAPI introduces a distinct method of authentication for external apps. +This authentication relies on a shared secret between Nextcloud and the external app + +Authentication flow +^^^^^^^^^^^^^^^^^^^ + +1. ExApp sends a request to Nextcloud +2. Nextcloud passes request to AppAPI +3. AppAPI validates request (see `authentication flow in details`_) +4. Request is accepted/rejected + +.. mermaid:: + + sequenceDiagram + participant ExApp + box Nextcloud + participant Nextcloud + participant AppAPI + end + ExApp->>+Nextcloud: Request to API + Nextcloud->>+AppAPI: Validate request + AppAPI-->>-Nextcloud: Request accepted/rejected + Nextcloud-->>-ExApp: Response (200/401) + + +.. _auth-headers: + +Authentication headers +^^^^^^^^^^^^^^^^^^^^^^ + +Each ExApp request to secured API with AppAPIAuth must contain the following headers: + +1. ``AA-VERSION`` - minimal version of the AppAPI +2. ``EX-APP-ID``- ID of the ExApp +3. ``EX-APP-VERSION`` - version of the ExApp +4. ``AUTHORIZATION-APP-API`` - base64 encoded ``userid:secret`` + +Authentication flow in details +****************************** + +.. mermaid:: + :zoom: + + sequenceDiagram + autonumber + participant ExApp + box Nextcloud + participant Nextcloud + participant AppAPI + end + ExApp->>+Nextcloud: Request to API + Nextcloud->>Nextcloud: Check if AUTHORIZATION-APP-API header exists + Nextcloud-->>ExApp: Reject if AUTHORIZATION-APP-API header not exists + Nextcloud->>Nextcloud: Check if AppAPI app is enabled + Nextcloud-->>ExApp: Reject if AppAPI is not exists or disabled + Nextcloud->>+AppAPI: Validate request + AppAPI-->>AppAPI: Check if ExApp exists and enabled + AppAPI-->>Nextcloud: Reject if ExApp not exists or disabled + AppAPI-->>AppAPI: Validate shared secret from AUTHORIZATION-APP-API + AppAPI-->>Nextcloud: Reject if secret does not match + AppAPI-->>AppAPI: Check if user is not empty and active + AppAPI-->>Nextcloud: Set active user + AppAPI->>-Nextcloud: Request accepted/rejected + Nextcloud->>-ExApp: Response (200/401) + + +AppAPIAuth +^^^^^^^^^^ + +AppAPI provides ``AppAPIAuth`` attribute with middleware to validate requests from ExApps. +In your API controllers you can use it as an PHP attribute. + +AppAPI session keys +^^^^^^^^^^^^^^^^^^^ + +After successful authentication AppAPI sets `app_api` session key to ``true``. + +.. code-block:: php + + $this->session->set('app_api', true); + $this->session->set('app_api_system', true); // deprecated since AppAPI 3.0.0 + +.. note:: + + The Nextcloud server verifies this session key and allows **CORS protection** and **Two-Factor authentication** to be bypassed for requests coming from ExApps. + Also the rate limit is not applied to requests coming from ExApps. diff --git a/_sources/tech_details/Deployment.rst.txt b/_sources/tech_details/Deployment.rst.txt new file mode 100644 index 00000000..37b0faed --- /dev/null +++ b/_sources/tech_details/Deployment.rst.txt @@ -0,0 +1,159 @@ +.. _app_deployment: + +Deployment +========== + +Overview +-------- + +AppAPI ExApps deployment process in short consists of 2 steps: + +1. `DaemonConfig registration`_ +2. `ExApp registration`_ + +.. _occ_daemon_config_registration: + +DaemonConfig registration +------------------------- + +The first step is to register DaemonConfig, where your ExApps will be deployed. +Before that you will need to configure your Docker socket to be accessible by Nextcloud instance and webserver user. +In case of remote Docker Engine API, you will need to expose it so it is accessible by Nextcloud instance and import certificates. + +.. note:: + For now only Docker daemon ``accepts-deploy-id: docker-install`` is supported. + For development and manually deployed app in docker there is ``accepts-deploy-id: manual-install``. + +This can be done by ``occ`` CLI command **app_api:daemon:register**: + +.. code-block:: bash + + app_api:daemon:register [--net NET] [--haproxy_password PASSWORD] [--] + +Arguments +********* + + * ``name`` - unique name of the daemon (e.g. ``docker_local_sock``) + * ``display-name`` - name of the daemon (e.g. ``My Local Docker``, will be displayed in the UI) + * ``accepts-deploy-id`` - type of deployment (``docker-install`` or ``manual-install``) + * ``protocol`` - protocol used to connect to the daemon (``http`` or ``https``) + * ``host`` - host of the daemon (e.g. ``/var/run/docker.sock`` or ``host:port``) + * ``nextcloud_url`` - Nextcloud URL, Daemon config required option (e.g. ``https://nextcloud.local``) + +Options +******* + + * ``--net [network-name]`` - ``[required]`` network name to bind docker container to (default: ``host``) + * ``--haproxy_password PASSWORD`` - ``[optional]`` password if ``AppAPI Docker Socket Proxy`` is used + * ``--gpu`` - ``[optional]`` GPU device to expose to the daemon (e.g. ``/dev/dri``) + +.. note:: + Common configurations are tested by CI in our repository, see `workflows on github `_. + +Example +******* + +Example of ``occ`` **app_api:daemon:register** command: + +.. code-block:: bash + + sudo -u www-data php occ app_api:daemon:register docker_local_sock "My Local Docker" docker-install http /var/run/docker.sock "https://nextcloud.local" --net nextcloud + + +ExApp registration +------------------ + +Second and final step is to deploy and register ExApp in Nextcloud on previously registered daemon. +This can be done by ``occ`` CLI command **app_api:app:register**: + +.. code-block:: bash + + app_api:app:register [--force-scopes] [--] + +Arguments +********* + + * ``appid`` - unique name of the ExApp (e.g. ``app_python_skeleton``, must be the same as in deployed container) + * ``daemon-config-name`` - unique name of the daemon (e.g. ``docker_local_sock``) + +Options +******* + + * ``--force-scopes`` **[optional]** - force scopes approval + * ``--info-xml INFO-XML`` **[optional]** - path to info.xml file with ExApp description (url or local absolute path) + * ``--json-info JSON-INFO`` **[optional]** - JSON with ExApp description + +.. warning:: + After successful deployment (pull, create and start container), there is a heartbeat check with 90 seconds timeout (will be configurable). + +Manual install for development +****************************** + +For development purposes, you can install ExApp manually. +There is a ``manual-install`` DeployConfig type, which can be used in case of development. +For ExApp registration with it you need to provide JSON app info or a path to app XML file. + +For all examples and applications we release we usually add manual_install command in it's makefile for easier development. + +.. code-block:: + + php occ app_api:app:register nc_py_api manual_install --json-info \ + "{\"id\":\"nc_py_api\",\"name\":\"nc_py_api\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"port\":$APP_PORT,\"scopes\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\", \"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\"],\"system\":1}" \ + --force-scopes + +.. note:: **Deployment/Startup of App should be done by developer when ``manual-install`` DeployConfig type is used.** + +.. _ex_app_env_vars: + +Deploy env variables +******************** + +Deploy env variables are used to configure ExApp container. +The following env variables are required and built automatically: + + * ``AA_VERSION`` - AppAPI version + * ``APP_SECRET`` - generated shared secret used for AppAPI authentication + * ``APP_ID`` - ExApp appid + * ``APP_DISPLAY_NAME`` - ExApp display name + * ``APP_VERSION`` - ExApp version + * ``APP_HOST`` - host ExApp is listening on + * ``APP_PORT`` - port ExApp is listening on (randomly selected by AppAPI) + * ``APP_PERSISTENT_STORAGE`` - path to mounted volume for persistent data storage between ExApp updates + * ``NEXTCLOUD_URL`` - Nextcloud URL to connect to + +Application installation scheme +------------------------------- + +1. AppAPI deploys the application and launches it. +2. AppAPI for `N` seconds (default ``90``) checks the ``/heartbeat`` endpoint with ``GET`` request. +3. AppAPI sends a ``POST`` to the ``/init`` endpoint. + + .. note:: if ExApp do not implements ``/init`` endpoint and + AppAPI receives 501 or 404 status error, AppAPI enables the application by going to point 5. + +4. **ExApp** sends an integer from ``0`` to ``100`` to the OCS endpoint ``apps/app_api/apps/status`` indicating the initialization progress. After sending ``100``, the application is considered initialized. +5. AppAPI sends a PUT to the ``/enabled`` endpoint. + +ExApp info.xml schema +--------------------- + +ExApp info.xml (`example `_) file is used to describe ExApp params. +It is used to generate ExApp docker container and to register ExApp in Nextcloud. +It has the same structure as Nextcloud appinfo/info.xml file, but with some additional fields: + +.. code-block:: xml + + ... + + + ghcr.io + cloud-py-api/talk_bot + latest + + // deprecated since AppAPI 3.2.0 + TALK + TALK_BOT + + 0 // deprecated since AppAPI 3.0.0 + + ... diff --git a/_sources/tech_details/Glossary.rst.txt b/_sources/tech_details/Glossary.rst.txt new file mode 100644 index 00000000..9135c970 --- /dev/null +++ b/_sources/tech_details/Glossary.rst.txt @@ -0,0 +1,14 @@ +Glossary +======== + +AppAPI brings out the following terms frequently used in the code: + +* ``ExApp`` (External App) - the app on another (from PHP) programming language, which uses AppAPI OCS API +* ``DaemonConfig`` - configuration of orchestration daemon (e.g. Docker) where ExApps are deployed +* ``DeployConfig`` - additional DaemonConfig options for orchestrator (e.g. network) and ExApps (nextcloud_url, host, etc.) +* ``ExAppConfig`` - similar to Nextcloud `app_config`, but for ExApps configuration +* ``ExAppPreferences`` - similar to Nextcloud `app_preferences`, user-specific settings for ExApps +* ``AppAPIAuth`` - AppAPI authentication +* ``ExAppScope`` - granted to ExApp scope group of access to API routes +* ``ExAppApiScope`` - pre-defined scope group of access to list of API routes +* ``FileActionsMenu`` - entry in files actions menu (context menu) diff --git a/_sources/tech_details/InstallationFlow.rst.txt b/_sources/tech_details/InstallationFlow.rst.txt new file mode 100644 index 00000000..c016dd84 --- /dev/null +++ b/_sources/tech_details/InstallationFlow.rst.txt @@ -0,0 +1,112 @@ +.. _app_installation_flow: + +App Installation Flow +===================== + +Image Pulling(Docker) +--------------------- + +AppAPI **2.5.0+** will always first try to pull a docker image with a ``suffix`` equal to value of *computeDevice*. + +Let us remind you that ``computeDevice`` can take the following values: ``cpu``, ``cuda``, ``rocm`` + +The suffix will be added as follows: + +.. code:: + + return $imageParams['image_src'] . '/' . + $imageParams['image_name'] . '-' . $daemonConfig['computeDevice']['id'] . ':' . $imageParams['image_tag']; + +For ``cpu`` AppAPI will first try to get the image from ``ghcr.io/cloud-py-api/skeleton-cpu:latest``. +In case the image is not found, ``ghcr.io/cloud-py-api/skeleton:latest`` will be pulled. + +If you as an application developer want to have a custom images for any of these values, you can push that extended images to registry in addition to the based one. + +Heartbeat +--------- + +The first thing AppAPI does is deploy of the application. + +In the case of ``Docker``, this is: + +#. 1. performing an image pull +#. 2. creating container from the docker image +#. 3. if the container supports `healthcheck` - AppAPI waits for the `healthy` status +#. 4. waiting until the “/heartbeat” endpoint becomes available with a ``GET`` request + +The application, in response to the request "/heartbeat", should return json: ``{"status": "ok"}``. + +.. note:: The request to ``/heartbeat`` endpoint is made without AppAPI authentication. + +Init +---- + +.. note:: Starting from this point, all requests made by AppAPI contains :ref:`auth-headers`. + +After application is ready, which is determined by previous step, +AppAPI sends ``POST`` request to the ``/init`` application endpoint. + +*If the application does not need to carry out long initialization, it has an option to not implement "/init" endpoint, so +AppAPI will get 404 or 501 error on it's request, and consider that initialization is done and this section can be skipped.* + +In case you want to implement "/init" endpoint, your application should: + +1. In "/init" handler: Response with empty JSON on AppAPI call. +2. In background job: Send an ``OCS request`` to ``PUT /ocs/v1.php/apps/app_api/ex-app/status`` with the progress value. + +.. warning:: + + ``PUT /ocs/v1.php/apps/app_api/apps/status/$APP_ID`` is deprecated and will be removed in the future. + +Possible values for **progress** are integers from 1 to 100; +after receiving the value 100, the **application is considered initialized and ready to work**. + +If at the initialization stage the application has a critical error due to which its further operation is impossible, + +``"error": "some error"`` + +should be added to the ``OCS request`` for setting progress, +with a short explanation at what stage this error occurred. + +Example of request payload with error will look like this:: + + {"progress": 67, "error": "connection error to huggingface."} + +Enabled +------- + +After receiving **progress: 100** (*or when ExApp is not implementing "/init" endpoint*), AppAPI enables the application. + +To enable or disable the application, a PUT request is sent to the ``/enabled`` endpoint. + +.. note:: Unlike using a payload, this request utilizes a query parameter named ``enabled`` to specify the desired state. + +The ``enabled`` parameter accepts an integer value: + +* `1` to enable the application +* `0` to disable the application + +For example, to enable the application, the request would be:: + + PUT http://expapp:2432/enabled?enabled=1 + +Similarly, to disable the application, the request would be:: + + PUT http://expapp:2432/enabled?enabled=0 + +This approach ensures that the application's state can be easily toggled using a simple query parameter. + +.. note:: ``/enabled`` endpoint shares both **enabling** and **disabling**, + so app should determine what is going on using the ``enabled`` input parameter of the request. + +Inside ``/enabled`` handler application should register all actions related to the Nextcloud, like UI and all other stuff. + +Response for this request should contain:: + + {"error": ""} + +for success and if some error occur during **enabling**, it should be present and not be empty:: + + {"error": "i cant handle enabling"} + +This is all three steps involved in the applications installation flow. diff --git a/_sources/tech_details/Translations.rst.txt b/_sources/tech_details/Translations.rst.txt new file mode 100644 index 00000000..5b3ef0a5 --- /dev/null +++ b/_sources/tech_details/Translations.rst.txt @@ -0,0 +1,50 @@ +Translations +============ + +ExApps translations work in the `same way as for PHP apps `_ with a few adjustments +and differences. + +In short, you just have to provide a ``l10n/.js`` (for front-end) and ``l10n/.json`` (for back-end) files for your app. + + +Front-end +********* + +For the front-end part AppAPI will inject the current user's locale ``l10n/.js`` script, so that access to translated strings in kept the same as was before in PHP apps. + +.. note:: + + ExApp l10n files are included only on the ExApp UI pages (:ref:`Top Menu `), Files (for :ref:`FileAction `) and Settings (for :ref:`DeclarativeSettings `). + + +Back-end +******** + +For the back-end part of ExApp which can be written in different programming languages it is **up to the developer to decide** how to handle and translations files. +There is an example repository with translations: `UI example with translations `_. + + +Manual install +************** + +For ``manual-install`` type administrator will have to manually extract to the server's `writable apps directory `_ ``l10n`` folder of ExApp +(e.g. ``/path/to/apps-writable//l10n/*.(js|json)``). +This will allow server to access ExApp's strings with translations. + +.. note:: + + Only ``l10n`` folder must be present on the server side, ``appinfo/info.xml`` could lead to be misdetected by server as PHP app folder. + + + +Docker install +************** + +For ``docker-install`` type AppAPI will extract ``l10n`` folder to the server automatically during installation from ExApp release archive. + + +Translation tool +**************** + +To add support for your language in Nextcloud `translationtool `_ feel free to create an issue in the `nextcloud/docker-ci `_ repository +or open a pull request with the changes made in ``createPotFile`` function to extract and convert translation strings. diff --git a/_sources/tech_details/api/appconfig.rst.txt b/_sources/tech_details/api/appconfig.rst.txt new file mode 100644 index 00000000..5e8e8356 --- /dev/null +++ b/_sources/tech_details/api/appconfig.rst.txt @@ -0,0 +1,136 @@ +========= +AppConfig +========= + +ExApp AppConfig API is similar to the standard Nextcloud **appconfig** API. + +Set app config value +^^^^^^^^^^^^^^^^^^^^ + +Set or update ExApp config value. + +.. note:: when ``sensitive`` is not specified during updating value, it will be not changed to default. + +OCS endpoint: ``POST /apps/app_api/api/v1/ex-app/config`` + +Request data +************ + +.. code-block:: json + + { + "configKey": "key", + "configValue": "value" + "sensitive": "sensitive flag affecting the visibility of the value (0/1, default: 0)" + } + +Response data +************* + +On success, ExAppConfig object is returned. +On error OCS Bad Request is returned. + +.. code-block:: json + + { + "ocs": + { + "meta": + { + "status":"ok", + "statuscode":100, + "message":"OK", + "totalitems":"", + "itemsperpage":"" + }, + "data": + { + "id":1084, + "appid":"app_id", + "configkey":"key", + "configvalue":"value", + "sensitive":1 + } + } + } + +Get app config values +^^^^^^^^^^^^^^^^^^^^^ + +Get ExApp config values + +OCS endpoint: ``POST /apps/app_api/api/v1/ex-app/config/get-values`` + +Request data +************ + +.. code-block:: json + + { + "configKeys": ["key1", "key2", "key3"] + } + +Response data +************* + +List of ExApp config values are returned. + +.. code-block:: json + + { + "ocs": + { + "meta": + { + "status":"ok", + "statuscode":100, + "message":"OK", + "totalitems":"", + "itemsperpage":"" + }, + "data":[ + { + "configkey":"test_key", + "configvalue":"123" + } + ] + } + } + +Delete app config values +^^^^^^^^^^^^^^^^^^^^^^^^ + +Delete ExApp config values. + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ex-app/config`` + +Request data +************ + +.. code-block:: json + + { + "configKeys": ["key1", "key2", "key3"] + } + +Response +******** + +Returns the number of configuration values removed. + +.. code-block:: json + + { + "ocs": + { + "meta": + { + "status":"ok", + "statuscode":100, + "message":"OK", + "totalitems":"", + "itemsperpage":"" + }, + "data":1 + } + } diff --git a/_sources/tech_details/api/events_listener.rst.txt b/_sources/tech_details/api/events_listener.rst.txt new file mode 100644 index 00000000..042cc72f --- /dev/null +++ b/_sources/tech_details/api/events_listener.rst.txt @@ -0,0 +1,83 @@ +.. _events_listener: + +=============== +Events Listener +=============== + +This API allows you to listen to `Nextcloud events `_ + +Currently only **limited** numbers of events are supported. + +Please let us know if there are any specific event we should add support to. + +.. note:: + + Unlike PHP events, all information from events comes to ExApp **asynchronously**, more like a notification system + to no slow down the server. + +Register +^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/events_listener`` + +Params +****** + +.. code-block:: json + + { + "eventType": "node_event", + "actionHandler": "/action_handler_route" + "eventSubtypes": [], + } + +.. note:: ``eventSubtypes`` is an optional parameter, when it is not specified all event subtypes will be propagated to ExApp. + + Url in ``actionHandler`` is relative to the ExApp root, starting slash is not required. + +Unregister +^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/events_listener`` + +Params +****** + +To unregister EventsListener, you just need to provide an `eventType` of the registered EventsListener: + +.. code-block:: json + + { + "eventType": "node_event" + } + +Event payload +^^^^^^^^^^^^^ + +.. code-block:: json + + { + "event_type": "node_event", + "event_subtype": "NodeCreatedEvent", + "event_data": "associative array depending on `event_subtype`" + } + +Events types +^^^^^^^^^^^^ + +Node Events +*********** + +``node_event`` - events about File `Nodes` + +Supported event sub-types: + * ``NodeCreatedEvent`` + * ``NodeTouchedEvent`` + * ``NodeWrittenEvent`` + * ``NodeDeletedEvent`` + * ``NodeRenamedEvent`` + * ``NodeCopiedEvent`` + +For all Node events ``event_data`` contain key **target** which has the same format like in :ref:`FileActionsMenu payload ` + +For ``NodeCopiedEvent`` and ``NodeRenamedEvent`` there is also a ``source`` key in the same format. diff --git a/_sources/tech_details/api/exapp.rst.txt b/_sources/tech_details/api/exapp.rst.txt new file mode 100644 index 00000000..d3510d3c --- /dev/null +++ b/_sources/tech_details/api/exapp.rst.txt @@ -0,0 +1,139 @@ +===== +ExApp +===== + +OCS APIs for ExApp actions. + +Get ExApps list +^^^^^^^^^^^^^^^ + +Get list of installed ExApps. + +OCS endpoint: ``GET /apps/app_api/api/v1/ex-app/{list}`` + +There are two ``list`` options: + +- ``enabled``: list only enabled ExApps +- ``all``: list all ExApps + + +Response data +************* + +The response data is a JSON array of ExApp objects with the following attributes: + +.. code-block:: json + + { + "id": "appid of the ExApp", + "name": "name of the ExApp", + "version": "version of the ExApp", + "enabled": "true/false flag", + "last_check_time": "timestamp of last successful Nextcloud->ExApp connection check", + "system": "true/false flag indicating system ExApp", + } + +Set ExApp init progress +^^^^^^^^^^^^^^^^^^^^^^^ + +Used during ExApp :ref:`initialization step `. + +.. note:: + + AppAPIAuth required. + +OCS endpoint: ``PUT /apps/app_api/ex-app/status`` + +Request data +************ + +.. code-block:: json + + { + "progress": "progress value", + "error": "optional, error string message" + } + +Response data +************* + +Returns HTTP 200 on success, HTTP 404 - on error. + +Get Nextcloud URL +^^^^^^^^^^^^^^^^^ + +It might be necessary for ExApp to know (or update) the Nextcloud URL. + +OCS endpoint: ``GET /apps/app_api/api/v1/info/nextcloud_url`` + +Response data +************* + +Returns the base URL of the Nextcloud instance: + +.. code-block:: json + + { + "base_url": "http(s)://nextcloud.example.com" + } + + +Make Requests to ExApps +^^^^^^^^^^^^^^^^^^^^^^^ + +There are two endpoints for making requests to ExApps: + +1. Synchronous request: ``POST /apps/app_api/api/v1/ex-app/request/{appid}`` +2. Synchronous request with ExApp user setup: ``POST /apps/app_api/api/v1/ex-app/request/{appid}/{userId}`` + +Request data +************ + +The request data params are the same as in ``lib/PublicFunction.php``: + +.. code-block:: json + + { + "route": "relative route to ExApp API endpoint", + "method": "GET/POST/PUT/DELETE", + "params": {}, + "options": {}, + } + +.. note:: + + ``userId`` and ``appId`` is taken from url params + + +Response data +************* + +Successful request to ExApp OCS data response structure is the following: + +.. code-block:: json + + { + "status_code": "HTTP status code", + "body": "response data from ExApp", + "headers": "response headers from ExApp", + } + +If there is an error, the response object will have only an ``error`` attribute with the error message. + + +Get ExApp enabled status +^^^^^^^^^^^^^^^^^^^^^^^^ + +Return the enabled status of the authenticated ExApp. + +OCS endpoint: ``GET /apps/app_api/api/v1/ex-app/state`` + +.. note:: + + This endpoint can be called by ExApp even if it is disabled on the Nextcloud side, + and requires :ref:`AppAPIAuth `. + +Response data +************* + +Returns 1 if the ExApp is enabled, 0 if it is disabled. diff --git a/_sources/tech_details/api/fileactionsmenu.rst.txt b/_sources/tech_details/api/fileactionsmenu.rst.txt new file mode 100644 index 00000000..dbc4a55c --- /dev/null +++ b/_sources/tech_details/api/fileactionsmenu.rst.txt @@ -0,0 +1,155 @@ +.. _file_actions_menu_section: + +================= +File Actions Menu +================= + +FileActionsMenu is a simple API for registering entry to the file actions menu for ExApps. +AppAPI takes responsibility to register FileActionsMenu, ExApps needs only to register it in AppAPI. + +.. note:: + + FileActionsMenu rendered only for enabled ExApps. + +Register +^^^^^^^^ + +.. note:: + + With AppAPI 2.6.0 there is a new v2 OCS endpoint with redirect to ExApp UI support: + OCS endpoint: ``POST /apps/app_api/api/v2/ui/files-actions-menu``. + Old v1 is marked as deprecated. + +OCS endpoint: ``POST /apps/app_api/api/v1/ui/files-actions-menu`` + + +Params +****** + +Complete list of params (including optional): + +.. code-block:: json + + { + "name": "unique_name_of_file_actions_menu", + "displayName": "Display name (for UI listing)", + "actionHandler": "/action_handler_route" + "mime": "mime of files where to display action menu", + "icon": "img/icon.svg", + "permissions": "permissions", + "order": "order_in_file_actions_menu", + } + +.. note:: Urls ``icon`` and ``actionHandler`` are relative to the ExApp root, starting slash is not required. + +Optional params +*************** + + * `permissions` - File permissions required to display action menu, default: **31** (all permissions) + * `order` - Order in file actions menu, default: **0** + * `icon` - Url to icon, default: **null** + * `mime` - One mime or mimes separated by commas, default: **file** + +Unregister +^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/files-actions-menu`` + +Params +****** + +To unregister FileActionsMenu, you just need to provide name of registered FileActionsMenu: + +.. code-block:: json + + { + "name": "unique_name_of_file_action_menu" + } + +.. _node_info: + +Action payload to ExApp +^^^^^^^^^^^^^^^^^^^^^^^ + +When FileActionsMenu invoked, AppAPI forwards action for handling to ExApp. +The following data is sent to ExApp FileActionsMenu handler from the context of action: + +.. code-block:: json + + { + "fileId": "123", + "name": "filename", + "directory": "relative/to/user/path/to/directory", + "etag": "file_etag", + "mime": "file_full_mime", + "fileType": "dir/file", + "mtime": "last modify time(integer)", + "size": "integer", + "favorite": "nc_favorite_flag", + "permissions": "file_permissions_for_owner", + "shareOwner": "optional, str", + "shareOwnerId": "optional, str", + "shareTypes": "optional, int", + "shareAttributes": "optional, int", + "sharePermissions": "optional, int", + "userId": "string", + "instanceId": "string", + } + +Redirect to ExApp UI page (top menu) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. note:: + Supported only for Nextcloud 28+. + +If you want to open some files in ExApp UI, your FileActionsMenu have to be registered using OCS v2 version (``/apps/app_api/api/v2/ui/files-actions-menu``). + +After that, AppAPI will expect in the JSON response of the ExApp ``action_handler`` +the ``redirect_handler`` - a relative path on the ExApp Top Menu page, +to which AppAPI will attach a ``fileIds`` query parameter with the selected file ids, for example: + +``/index.php/apps/app_api/embedded/ui_example/first_menu/second_page?fileIds=123,124,125``, + +where the ``first_menu`` is the name of the Top Menu ExApp UI page, +and the ``second_page`` relative route handled on the frontend routing of the ExApp, +the ``fileIds`` query parameter contains the selected file ids separated by commas. +After that you can get the files info via webdav search request, see `ui_example `_. + + +Request flow +^^^^^^^^^^^^ + +General workflow of ExApp based on FileActionsMenu. + +User action +*********** + +.. mermaid:: + + sequenceDiagram + User->>FileActionMenu: Press on registered ExApp action + FileActionMenu->>AppAPI: send action context payload + AppAPI->>ExApp: forward request to handler + ExApp->>AppAPI: handler accepted action status + AppAPI->>User: Alert (action sent or error) + + +Action results +************** + +File processing results could be stored next to initial file or anywhere else, +e.g. on configured location in ExApp settings (``appconfig_ex``) or ExApp user settings (``preferences_ex``). + +.. mermaid:: + + sequenceDiagram + ExApp->>Nextcloud: Upload result file + ExApp->>AppAPI: Send notification about action results + +Examples +^^^^^^^^ + +Here is a list of simple example ExApps based on FileActionsMenu: + +* `to_gif `_ - ExApp based on FileActionsMenu to convert videos to gif in place +* `upscaler_example `_ - ExApp based on FileActionsMenu to upscale image in place diff --git a/_sources/tech_details/api/index.rst.txt b/_sources/tech_details/api/index.rst.txt new file mode 100644 index 00000000..3e47fa80 --- /dev/null +++ b/_sources/tech_details/api/index.rst.txt @@ -0,0 +1,30 @@ +.. _app_api_nextcloud_apis: + +===================== +AppAPI Nextcloud APIs +===================== + +.. note:: + + AppAPIAuth is required for all AppAPI OCS APIs, except ``ExApp``. + +.. toctree:: + :maxdepth: 2 + + logging + appconfig + preferences + exapp + routes + utils + fileactionsmenu + topmenu + settings + notifications + events_listener + occ_command + talkbots + speechtotext + textprocessing + machinetranslation + other_ocs diff --git a/_sources/tech_details/api/logging.rst.txt b/_sources/tech_details/api/logging.rst.txt new file mode 100644 index 00000000..623422bc --- /dev/null +++ b/_sources/tech_details/api/logging.rst.txt @@ -0,0 +1,35 @@ +======= +Logging +======= + +There is a logging API that can be used to log messages from ExApps in Nextcloud. + +.. note:: + + You can retrieve Nextcloud `loglevel` for internal ExApp usage + from private `app_api` (after authentication) capabilities + +Send log message (OCS) +^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/log`` + +Request data +************ + +.. code-block:: json + + { + "level": "log_lvl(integer)", + "message": "message", + } + + +The possible value of ``log_lvl`` is described here: `Nextcloud Log level `_ + +Response data +************* + +If no error occurs, empty response with result code 200 is returned. +If ExApp is not found or disable, or the loglevel is invalid - OCS Bad Request is returned. + diff --git a/_sources/tech_details/api/machinetranslation.rst.txt b/_sources/tech_details/api/machinetranslation.rst.txt new file mode 100644 index 00000000..7c9fd27a --- /dev/null +++ b/_sources/tech_details/api/machinetranslation.rst.txt @@ -0,0 +1,82 @@ +=================== +Machine Translation +=================== + +AppAPI provides a Machine-Translation providers registration mechanism for ExApps. + +.. note:: + + Available since Nextcloud 29. + +Registering translation provider (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/ai_provider/translation`` + +Request data +************ + +.. code-block:: json + + { + "name": "unique_provider_name", + "display_name": "Provider Display Name", + "from_languages": { + "en": "English", + "fr": "French", + }, + "to_languages": { + "en": "English", + "fr": "French", + }, + "action_handler": "/handler_route_on_ex_app", + "action_detect_lang": "/detect_lang_from_text_handler", + } + +.. note:: + + ``from_languages`` and ``to_languages`` are JSON object with language code as key and language name as value. + ``action_detect_lang`` is optional. If provided, server's translation manager will call this handler to detect language from text if no source lang provided, + for reference see `Providing Language detection `_. + + +Response +******** + +On successful registration response with status code 200 is returned. + +Unregistering translation provider (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ai_provider/translation`` + +Request data +************ + +.. code-block:: json + + { + "name": "unique_provider_name", + } + +Response +******** + +On successful unregister response with status code 200 is returned. + + +Report translation result (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``PUT /apps/app_api/api/v1/ai_provider/translation`` + +Request data +************ + +.. code-block:: json + + { + "task_id": "queued_task_id", + "result": "translated_text", + "error": "error_message_if_any", + } diff --git a/_sources/tech_details/api/notifications.rst.txt b/_sources/tech_details/api/notifications.rst.txt new file mode 100644 index 00000000..cde06b48 --- /dev/null +++ b/_sources/tech_details/api/notifications.rst.txt @@ -0,0 +1,63 @@ +============= +Notifications +============= + +AppAPI allows ExApps to send limited notifications to users. +ExApp can send simple notification using available `rich object strings `_. +More info about rich objects string can be found `here `_. + +Send notification (OCS) +^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/notification`` + +Request payload +*************** + +Example payload. + +.. code-block:: json + + { + "params": { + "object": "app_api", + "object_id": "app_api_id", + "subject_type": "app_api_ex_app", + "subject_params": { + "rich_subject": "Image {file} successfully upscaled!", + "rich_subject_params": { + "file": { + "type": "file", + "id": 123, + "name": "upscaled_image_name", + "path": "path/to/upscaled_image_name" + } + }, + "rich_message": "{user} checkout results!", + "rich_message_params": { + "user": { + "type": "user", + "id": "admin", + "name": "admin" + } + }, + "link": "http(s)://nextcloud.local/index.php/apps/files/?fileid=123" + } + } + } + + +Params +^^^^^^ + +Required payload params: + + * ``object`` - ``[required]`` should be set to default value, not used yet + * ``object_id`` - ``[required]`` should be set to default value, not used yet + * ``subject_type`` - ``[required]`` subject type should be set to default value, not used yet + * ``subject_params`` - ``[required]`` + * ``rich_subject`` - ``[optional]`` rich subject (title) string + * ``rich_subject_params`` - ``[optional]`` rich subject (title) params to replace rich objects in string + * ``rich_message`` - ``[optional]`` rich message string + * ``rich_message_params`` - ``[optional`` rich message params to replace objects in string + * ``link`` - absolute url to set for notification link diff --git a/_sources/tech_details/api/occ_command.rst.txt b/_sources/tech_details/api/occ_command.rst.txt new file mode 100644 index 00000000..46e09cb5 --- /dev/null +++ b/_sources/tech_details/api/occ_command.rst.txt @@ -0,0 +1,108 @@ +.. _occ_command: + +=========== +OCC Command +=========== + +This API allows you to register the occ (CLI) commands. +The principal is similar to the regular Nextcloud OCC command for PHP apps, that are working in context of the Nextcloud instance, +but for ExApps it is a trigger via Nextcloud OCC interface to perform some action on the External App side. + + +.. note:: + + Passing files directly as an input argument to the occ command is not supported. + +Register +^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/occ_command`` + +Params +****** + +.. code-block:: json + + { + "name": "appid:unique:command:name", + "description": "Description of the command", + "hidden": "1/0", + "arguments": [ + { + "name": "argument_name", + "mode": "required/optional/array", + "description": "Description of the argument", + "default": "default_value" + } + ], + "options": [ + { + "name": "option_name", + "shortcut": "s", + "mode": "required/optional/none/array/negatable", + "description": "Description of the option", + "default": "default_value" + } + ], + "usages": [ + "occ appid:unique:command:name argument_name --option_name", + "occ appid:unique:command:name argument_name -s" + ], + "execute_handler": "handler_route" + } + +For more details on the command arguments and options modes, +see the original docs for the Symfony console input parameters, which are actually being built from the provided data: +`https://symfony.com/doc/current/console/input.html#using-command-arguments `_ + + +Example +******* + +Lets assume we have a command `ping` that takes an argument `test_arg` and has an option `test-option`: + +.. code-block:: json + + { + "name": "my_app_id:ping", + "description": "Test ping command", + "hidden": 0, + "arguments": [ + { + "name": "test_arg", + "mode": "required", + "description": "Test argument", + "default": 123 + } + ], + "options": [ + { + "name": "test-option", + "shortcut": "t", + "mode": "none", + "description": "Test option", + } + ], + "usages": [ + "occ my_app_id:ping 12345", + "occ my_app_id:ping 12345 --test-option", + "occ my_app_id:ping 12345 -t" + ], + "execute_handler": "handler_route" + } + +Unregister +^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/occ_command`` + +Params +****** + +To unregister OCC Command, you just need to provide a command `name`: + +.. code-block:: json + + { + "name": "occ_command_name" + } diff --git a/_sources/tech_details/api/other_ocs.rst.txt b/_sources/tech_details/api/other_ocs.rst.txt new file mode 100644 index 00000000..23773d4a --- /dev/null +++ b/_sources/tech_details/api/other_ocs.rst.txt @@ -0,0 +1,20 @@ +Other OCS APIs +============== + +With AppAPI authentication it is possible for ExApps to use any other OCS APIs, that doesn't require OCP implementation: + +.. note:: + + To access these APIs they have to be supported by AppAPI (see :ref:`api_scopes`), + and ExApp have to require granted access (in ``info.xml``) to them accordingly. + +1. Calendar +2. Contacts +3. File System & Tags +4. Shares +5. Notifications +6. Users & Groups +7. User & Weather status +8. Activities +9. Notes +10. Etc. diff --git a/_sources/tech_details/api/preferences.rst.txt b/_sources/tech_details/api/preferences.rst.txt new file mode 100644 index 00000000..d7b36faf --- /dev/null +++ b/_sources/tech_details/api/preferences.rst.txt @@ -0,0 +1,138 @@ +=========== +Preferences +=========== + +ExApp preferences API is similar to the standard preferences API. +It's a user specific settings. + + +Set user config value +^^^^^^^^^^^^^^^^^^^^^ + +Set or update config value for **current authenticated user**. + +OCS endpoint: ``POST /apps/app_api/api/v1/ex-app/preference`` + +Request data +************ + +.. code-block:: json + + { + "configKey": "key", + "configValue": "value" + } + +Response data +************* + +On success ExAppPreference object is returned. +On error OCS Bad Request is returned. + +.. code-block:: json + + { + "ocs": + { + "meta": + { + "status":"ok", + "statuscode":100, + "message":"OK", + "totalitems":"", + "itemsperpage":"" + }, + "data": + { + "id":983, + "appid":"app_id", + "configkey":"test key", + "configvalue":"123", + "sensitive":0 + } + } + } + +Get user config values +^^^^^^^^^^^^^^^^^^^^^^ + +Get config values for **current authenticated user**. + +OCS endpoint: ``POST /apps/app_api/api/v1/ex-app/preference/get-values`` + +Request data +************ + +.. code-block:: json + + { + "configKeys": ["key1", "key2", "key3"] + } + +Response data +************* + +List of ExApp preferences values are returned. + +.. code-block:: json + + { + "ocs": + { + "meta": + { + "status":"ok", + "statuscode":100, + "message":"OK", + "totalitems":"", + "itemsperpage":"" + }, + "data":[ + { + "configkey":"test key", + "configvalue":"123" + }, + { + "configkey":"test key2", + "configvalue":"321" + } + ] + } + } + + +Delete user config values +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Delete config values for **current authenticated user**. + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ex-app/preference`` + +Request data +************ + +.. code-block:: json + + { + "configKeys": ["key1", "key2", "key3"] + } + +Response +******** + +.. code-block:: json + + { + "ocs": + { + "meta": + { + "status":"ok", + "statuscode":100, + "message":"OK", + "totalitems":"", + "itemsperpage":"" + }, + "data":2 + } + } diff --git a/_sources/tech_details/api/routes.rst.txt b/_sources/tech_details/api/routes.rst.txt new file mode 100644 index 00000000..afca4f2a --- /dev/null +++ b/_sources/tech_details/api/routes.rst.txt @@ -0,0 +1,47 @@ +.. _ex_app_routes: + +====== +Routes +====== + +Since AppAPI 3.0.0 ExApps have to declare their routes allowed to be accessed via the AppAPI ExApp proxy. + +.. note:: + + This routes check applied only for ExApp proxy (``/apps/app_api/proxy/*``). + + +Register +^^^^^^^^ + +During ExApp installation, the ExApp routes are registered automatically. +The routes must be declared in the ``external-app`` - ``routes`` tag of the ``info.xml`` file. + +Example +******* + +.. code-block:: + + + + .* + GET,POST,PUT,DELETE + USER + [] + [401, 500] + + + +where the fields are: + +- ``url``: the route to be registered on the ExApp side, can be a regex +- ``verb``: the HTTP verb that the route will accept, can be a comma separated list of verbs +- ``access_level``: the name of the access level required to access the route, PUBLIC - public access without auth, USER - Nextcloud user auth required, ADMIN - admin user required +- ``headers_to_exclude``: a json encoded string of an array of strings, the headers that the ExApp wants to be excluded from the request to it +- ``bruteforce_protection``: a json encoded string of an array of numbers, the HTTP status codes that must trigger the bruteforce protection + + +Unregister +^^^^^^^^^^ + +ExApp routes are unregistered automatically when the ExApp is uninstalling, or during the ExApp update before registering the new routes. diff --git a/_sources/tech_details/api/settings.rst.txt b/_sources/tech_details/api/settings.rst.txt new file mode 100644 index 00000000..b379ac8b --- /dev/null +++ b/_sources/tech_details/api/settings.rst.txt @@ -0,0 +1,211 @@ +.. _declarative_settings_section: + +==================== +Declarative Settings +==================== + +Starting from Nextcloud **29**, AppAPI provides the ability to display ex-application settings. +When admin or user changes some ex-app settings +they will be stored in the database and can be received using :doc:`preferences` or :doc:`appconfig` API. + +.. note:: + + Settings rendered only for enabled ExApps. + +.. warning:: + + ``section_id`` from **scheme** should be already registered by any PHP application. + + **AppAPI** provides two sections for that: ``ai_integration_team`` and ``declarative_settings``, you can use them. + +Register Settings +^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/ui/settings`` + +Params +****** + +Complete list of params (including optional): + +.. code-block:: json + + { + "formScheme": "settings scheme" + } + +Unregister Menu Entry +^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/settings`` + +Params +****** + +.. code-block:: json + + { + "formId": "formId from scheme" + } + +Example of settings scheme in Python: + +.. code-block:: python + + { + "id": "settings_example", + "priority": 10, + "section_type": "admin", + "section_id": "ai_integration_team", + "title": "AppAPI declarative settings", + "description": "These fields are rendered dynamically from declarative schema", + "fields": [ + { + "id": "field1", + "title": "Multi-selection", + "description": "Select some option setting", + "type": 'multi-select', + "options": ["foo", "bar", "baz"], + "placeholder": "Select some multiple options", + "default": ["foo", "bar"], + }, + { + "id": "some_real_setting", + 'title': 'Choose init status check background job interval', + 'description': 'How often AppAPI should check for initialization status', + 'type': 'radio', + 'placeholder': 'Choose init status check background job interval', + 'default': '40m', + 'options': [ + { + 'name': 'Each 40 minutes', + 'value': '40m', + }, + { + 'name': 'Each 60 minutes', + 'value': '60m', + }, + { + 'name': 'Each 120 minutes', + 'value': '120m', + }, + { + 'name': 'Each day', + 'value': f"{60 * 24}m", + }, + ], + }, + { + 'id': 'test_ex_app_field_1', + 'title': 'Default text field', + 'description': 'Set some simple text setting', + 'type': 'text', + 'placeholder': 'Enter text setting', + 'default': 'foo', + }, + { + 'id': 'test_ex_app_field_1_1', + 'title': 'Email field', + 'description': 'Set email config', + 'type': 'email', + 'placeholder': 'Enter email', + 'default': '', + }, + { + 'id': 'test_ex_app_field_1_2', + 'title': 'Tel field', + 'description': 'Set tel config', + 'type': 'tel', + 'placeholder': 'Enter your tel', + 'default': '', + }, + { + 'id': 'test_ex_app_field_1_3', + 'title': 'Url (website) field', + 'description': 'Set url config', + 'type': url', + 'placeholder': 'Enter url', + 'default': '', + }, + { + 'id': 'test_ex_app_field_1_4', + 'title': 'Number field', + 'description': 'Set number config', + 'type': 'number', + 'placeholder': 'Enter number value', + 'default': 0, + }, + { + 'id': 'test_ex_app_field_2', + 'title': 'Password', + 'description': 'Set some secure value setting', + 'type': password', + 'placeholder': 'Set secure value', + 'default': '', + }, + { + 'id': 'test_ex_app_field_3', + 'title': 'Selection', + 'description': 'Select some option setting', + 'type': 'select', + 'options': ['foo', 'bar', 'baz'], + 'placeholder': 'Select some option setting', + 'default': 'foo', + }, + { + 'id': 'test_ex_app_field_4', + 'title': 'Toggle something', + 'description': 'Select checkbox option setting', + 'type': 'checkbox', + 'label': 'Verify something if enabled', + 'default': False, + }, + { + 'id': 'test_ex_app_field_5', + 'title': 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}', + 'description': 'Select checkbox option setting', + 'type': 'multi-checkbox', + 'default': {'foo': True, 'bar': True}, + 'options': [ + { + 'name':'Foo', + 'value': 'foo', + }, + { + 'name': 'Bar', + 'value': 'bar', + }, + { + 'name': 'Baz', + 'value': 'baz', + }, + { + 'name': 'Qux', + 'value': 'qux', + }, + ], + }, + { + 'id': 'test_ex_app_field_6', + 'title': 'Radio toggles, describing one setting like single select', + 'description': 'Select radio option setting', + 'type': 'radio', + 'label': 'Select single toggle', + 'default': 'foo', + 'options': [ + { + 'name': 'First radio', + 'value': 'foo' + }, + { + 'name': 'Second radio', + 'value': 'bar' + }, + { + 'name': 'Second radio', + 'value': 'baz' + }, + ], + }, + ] + } diff --git a/_sources/tech_details/api/speechtotext.rst.txt b/_sources/tech_details/api/speechtotext.rst.txt new file mode 100644 index 00000000..0c2669bc --- /dev/null +++ b/_sources/tech_details/api/speechtotext.rst.txt @@ -0,0 +1,51 @@ +============== +Speech-To-Text +============== + +AppAPI provides a Speech-To-Text (STT) provider registration API for the ExApps. + +.. note:: + + Available since Nextcloud 29. + +Registering ExApp STT provider (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/provider/speech_to_text`` + +Request data +************ + +.. code-block:: json + + { + "name": "unique_provider_name", + "display_name": "Provider Display Name", + "action_handler": "/handler_route_on_ex_app", + } + + +Response +******** + +On successful registration response with status code 200 is returned. + +Unregistering ExApp STT provider (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/provider/speech_to_text`` + +Request data +************ + +.. code-block:: json + + { + "name": "unique_provider_name", + } + + +Response +******** + +On successful unregister response with status code 200 is returned. diff --git a/_sources/tech_details/api/talkbots.rst.txt b/_sources/tech_details/api/talkbots.rst.txt new file mode 100644 index 00000000..c6825629 --- /dev/null +++ b/_sources/tech_details/api/talkbots.rst.txt @@ -0,0 +1,41 @@ +========= +Talk bots +========= + +AppAPI provides API for registering ExApps Talk bots. +This means that ExApps could be just as Talk bot or it could be as one of the options of the app. +Read more about Talk bots `here `_. + +Register ExApp Talk bot (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/talk_bot`` + +Request data +************ + +.. code-block:: json + + { + "name": "Talk bot display name", + "route": "/talk_bot_webhook_route_on_ex_app", + "description": "Talk bot description", + } + + +Unregister ExApp Talk bot (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To unregister the ExApp Talk bot, you will have to pass the route where the Talk bot is registered. + +OCS endpoint: ``DELETE /apps/app_api/api/v1/talk_bot`` + +Request data +************ + +.. code-block:: json + + { + "route": "/route_of_talk_bot" + } + diff --git a/_sources/tech_details/api/textprocessing.rst.txt b/_sources/tech_details/api/textprocessing.rst.txt new file mode 100644 index 00000000..a4a80802 --- /dev/null +++ b/_sources/tech_details/api/textprocessing.rst.txt @@ -0,0 +1,72 @@ +=============== +Text-Processing +=============== + +AppAPI provides a Text-Processing providers registration mechanism for ExApps. + +.. note:: + + Available since Nextcloud 29. + +Registering text-processing provider (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/ai_provider/text_processing`` + +Request data +************ + +.. code-block:: json + + { + "name": "unique_provider_name", + "display_name": "Provider Display Name", + "action_handler": "/handler_route_on_ex_app", + "task_type": "supported_task_type", + } + +.. note:: + + ``action_type`` is a class name of the Text-Processing task type that can be found in the list of supported task types. + +Response +******** + +On successful registration response with status code 200 is returned. + +Unregistering text-processing provider (OCS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ai_provider/text_processing`` + +Request data +************ + +.. code-block:: json + + { + "name": "unique_provider_name", + } + +Response +******** + +On successful unregister response with status code 200 is returned. + + +Get list of supported Text-Processing task types (capabilities) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There are limited number of Task Types that can be used for Text-Processing. +You can get list of supported Text-Processing task types from OCS capabilities. + +Response +******** + +.. code-block:: json + + { + "text_processing": { + "task_types": ["free_prompt", "headline", "summary", "topics"] + } + } diff --git a/_sources/tech_details/api/topmenu.rst.txt b/_sources/tech_details/api/topmenu.rst.txt new file mode 100644 index 00000000..22e572e1 --- /dev/null +++ b/_sources/tech_details/api/topmenu.rst.txt @@ -0,0 +1,160 @@ +.. _top_menu_section: + +============== +Top Menu Entry +============== + +TopMenu is an API for registering entry in the Nextcloud Top Menu for ExApps. +AppAPI takes responsibility to register TopMenu and proxy all requests to ExApp. + +.. note:: + + TopMenu rendered only for enabled ExApps. + +Register Menu Entry +^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/ui/top-menu`` + +Params +****** + +Complete list of params (including optional): + +.. code-block:: json + + { + "name": "unique_name_of_top_menu", + "displayName": "Display name", + "icon": "img/icon.svg", + "adminRequired": "0 or 1", + } + +.. note:: ``icon`` are relative to the ExApp root, starting slash is not required. + + +Optional params +*************** + + * `icon` - Url to icon, default: **null** + * `adminRequired` - Value indicating should be Entry visible to all or only to admins. + +Unregister Menu Entry +^^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/top-menu`` + +Params +****** + +To unregister TopMenu, you just need to provide name of registered TopMenu: + +.. code-block:: json + + { + "name": "unique_name_of_top_menu" + } + +Set Initial state +^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/ui/initial-state`` + +Params +****** + +.. code-block:: json + + { + "type": "top_menu", + "name": "unique_name_of_top_menu", + "key": "key_name", + "value": "array with value(s)", + } + +Remove Initial state +^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/initial-state`` + +Params +****** + +.. code-block:: json + + { + "type": "top_menu", + "name": "unique_name_of_top_menu", + "key": "key_name", + } + +Add script +^^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/ui/script`` + +Params +****** + +.. code-block:: json + + { + "type": "top_menu", + "name": "unique_name_of_script", + "path": "Url to script, e.g.: js/ui_example-main", + "afterAppId": "optional value", + } + +.. note:: Url to script is relative to the ExApp root, starting slash is not required, + ".js" extension is not needed and will be appended automatically by server. + +Remove script +^^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/script`` + +Params +****** + +.. code-block:: json + + { + "type": "top_menu", + "name": "unique_name_of_script", + "path": "Url to script", + } + +Add style +^^^^^^^^^ + +OCS endpoint: ``POST /apps/app_api/api/v1/ui/style`` + +Params +****** + +.. code-block:: json + + { + "type": "top_menu", + "name": "unique_name_of_style", + "path": "Url to style, e.g.: css/my-style", + } + +.. note:: Url to style is relative to the ExApp root, starting slash is not required, + ".css" extension is not needed and will be appended automatically by server. + +Remove style +^^^^^^^^^^^^ + +OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/style`` + +Params +****** + +.. code-block:: json + + { + "type": "top_menu", + "name": "unique_name_of_style", + "path": "Url to style", + } diff --git a/_sources/tech_details/api/utils.rst.txt b/_sources/tech_details/api/utils.rst.txt new file mode 100644 index 00000000..c9c3b8a9 --- /dev/null +++ b/_sources/tech_details/api/utils.rst.txt @@ -0,0 +1,30 @@ +====================== +Miscellaneous OCS APIs +====================== + +There are some system utils APIs required for ExApps internal logic. + + +Get list of NC users +^^^^^^^^^^^^^^^^^^^^ + +OCS endpoint: ``GET /apps/app_api/api/v1/users`` + +Response data +************* + +Returns a list of user IDs only. + +.. code-block:: json + + {"ocs": { + "meta": { + "status": "ok", + "statuscode": 100, + "message": "OK", + "totalitems": "", + "itemsperpage": "" + }, + "data": ["admin", "alice", "bob", "jane", "john"] + } + } diff --git a/_sources/tech_details/index.rst.txt b/_sources/tech_details/index.rst.txt new file mode 100644 index 00000000..6e6f7a06 --- /dev/null +++ b/_sources/tech_details/index.rst.txt @@ -0,0 +1,13 @@ +Technical details +================= + +.. toctree:: + :maxdepth: 2 + + Glossary + InstallationFlow + ApiScopes + Deployment + Authentication + Translations + api/index.rst diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..81415803 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..f316efcb --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/check-solid.svg b/_static/check-solid.svg new file mode 100644 index 00000000..92fad4b5 --- /dev/null +++ b/_static/check-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/_static/clipboard.min.js b/_static/clipboard.min.js new file mode 100644 index 00000000..54b3c463 --- /dev/null +++ b/_static/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 + + + + diff --git a/_static/copybutton.css b/_static/copybutton.css new file mode 100644 index 00000000..f1916ec7 --- /dev/null +++ b/_static/copybutton.css @@ -0,0 +1,94 @@ +/* Copy buttons */ +button.copybtn { + position: absolute; + display: flex; + top: .3em; + right: .3em; + width: 1.7em; + height: 1.7em; + opacity: 0; + transition: opacity 0.3s, border .3s, background-color .3s; + user-select: none; + padding: 0; + border: none; + outline: none; + border-radius: 0.4em; + /* The colors that GitHub uses */ + border: #1b1f2426 1px solid; + background-color: #f6f8fa; + color: #57606a; +} + +button.copybtn.success { + border-color: #22863a; + color: #22863a; +} + +button.copybtn svg { + stroke: currentColor; + width: 1.5em; + height: 1.5em; + padding: 0.1em; +} + +div.highlight { + position: relative; +} + +/* Show the copybutton */ +.highlight:hover button.copybtn, button.copybtn.success { + opacity: 1; +} + +.highlight button.copybtn:hover { + background-color: rgb(235, 235, 235); +} + +.highlight button.copybtn:active { + background-color: rgb(187, 187, 187); +} + +/** + * A minimal CSS-only tooltip copied from: + * https://codepen.io/mildrenben/pen/rVBrpK + * + * To use, write HTML like the following: + * + *

Short

+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/_static/copybutton.js b/_static/copybutton.js new file mode 100644 index 00000000..2ea7ff3e --- /dev/null +++ b/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = ` + ${messages[locale]['copy_success']} + + +` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = ` + ${messages[locale]['copy_to_clipboard']} + + + +` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/_static/copybutton_funcs.js b/_static/copybutton_funcs.js new file mode 100644 index 00000000..dbe1aaad --- /dev/null +++ b/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 00000000..c718cee4 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/dark.css b/_static/css/dark.css new file mode 100644 index 00000000..8866c07e --- /dev/null +++ b/_static/css/dark.css @@ -0,0 +1,1996 @@ +@media (prefers-color-scheme: dark) { + html { + background-color: #181a1b !important; + } + + html, body, input, textarea, select, button { + background-color: #181a1b; + } + + html, body, input, textarea, select, button { + border-color: #736b5e; + color: #e8e6e3; + } + + a { + color: #3391ff; + } + + table { + border-color: #545b5e; + } + + ::placeholder { + color: #b2aba1; + } + + input:-webkit-autofill, + textarea:-webkit-autofill, + select:-webkit-autofill { + background-color: #555b00 !important; + color: #e8e6e3 !important; + } + + ::selection { + background-color: #004daa !important; + color: #e8e6e3 !important; + } + + ::-moz-selection { + background-color: #004daa !important; + color: #e8e6e3 !important; + } + + /* Invert Style */ + .jfk-bubble.gtx-bubble, embed[type="application/pdf"] { + filter: invert(100%) hue-rotate(180deg) contrast(90%) !important; + } + + /* Override Style */ + .vimvixen-hint { + background-color: #7b5300 !important; + border-color: #d8b013 !important; + color: #f3e8c8 !important; + } + + ::placeholder { + opacity: 0.5 !important; + } + + /* Variables Style */ + :root { + --darkreader-neutral-background: #181a1b; + --darkreader-neutral-text: #e8e6e3; + --darkreader-selection-background: #004daa; + --darkreader-selection-text: #e8e6e3; + } + + /* Modified CSS */ + a:hover, + a:active { + outline-color: initial; + } + + abbr[title] { + border-bottom-color: initial; + } + + ins { + background-image: initial; + background-color: rgb(112, 112, 0); + color: rgb(232, 230, 227); + text-decoration-color: initial; + } + + mark { + background-image: initial; + background-color: rgb(204, 204, 0); + color: rgb(232, 230, 227); + } + + ul, + ol, + dl { + list-style-image: none; + } + + li { + list-style-image: initial; + } + + img { + border-color: initial; + } + + fieldset { + border-color: initial; + } + + legend { + border-color: initial; + } + + .chromeframe { + background-image: initial; + background-color: rgb(53, 57, 59); + color: rgb(232, 230, 227); + } + + .ir { + border-color: initial; + background-color: transparent; + } + + .visuallyhidden { + border-color: initial; + } + + .fa-border { + border-color: rgb(53, 57, 59); + } + + .fa-inverse { + color: rgb(232, 230, 227); + } + + .sr-only { + border-color: initial; + } + + .fa::before, + .wy-menu-vertical li span.toctree-expand::before, + .wy-menu-vertical li.on a span.toctree-expand::before, + .wy-menu-vertical li.current > a span.toctree-expand::before, + .rst-content .admonition-title::before, + .rst-content h1 .headerlink::before, + .rst-content h2 .headerlink::before, + .rst-content h3 .headerlink::before, + .rst-content h4 .headerlink::before, + .rst-content h5 .headerlink::before, + .rst-content h6 .headerlink::before, + .rst-content dl dt .headerlink::before, + .rst-content p.caption .headerlink::before, + .rst-content table > caption .headerlink::before, + .rst-content .code-block-caption .headerlink::before, + .rst-content tt.download span:first-child::before, + .rst-content code.download span:first-child::before, + .icon::before, + .wy-dropdown .caret::before, + .wy-inline-validate.wy-inline-validate-success .wy-input-context::before, + .wy-inline-validate.wy-inline-validate-danger .wy-input-context::before, + .wy-inline-validate.wy-inline-validate-warning .wy-input-context::before, + .wy-inline-validate.wy-inline-validate-info .wy-input-context::before { + text-decoration-color: inherit; + } + + a .fa, + a .wy-menu-vertical li span.toctree-expand, + .wy-menu-vertical li a span.toctree-expand, + .wy-menu-vertical li.on a span.toctree-expand, + .wy-menu-vertical li.current > a span.toctree-expand, + a .rst-content .admonition-title, + .rst-content a .admonition-title, + a .rst-content h1 .headerlink, + .rst-content h1 a .headerlink, + a .rst-content h2 .headerlink, + .rst-content h2 a .headerlink, + a .rst-content h3 .headerlink, + .rst-content h3 a .headerlink, + a .rst-content h4 .headerlink, + .rst-content h4 a .headerlink, + a .rst-content h5 .headerlink, + .rst-content h5 a .headerlink, + a .rst-content h6 .headerlink, + .rst-content h6 a .headerlink, + a .rst-content dl dt .headerlink, + .rst-content dl dt a .headerlink, + a .rst-content p.caption .headerlink, + .rst-content p.caption a .headerlink, + a .rst-content table > caption .headerlink, + .rst-content table > caption a .headerlink, + a .rst-content .code-block-caption .headerlink, + .rst-content .code-block-caption a .headerlink, + a .rst-content tt.download span:first-child, + .rst-content tt.download a span:first-child, + a .rst-content code.download span:first-child, + .rst-content code.download a span:first-child, + a .icon { + text-decoration-color: inherit; + } + + .wy-alert, + .rst-content .note, + .rst-content .attention, + .rst-content .caution, + .rst-content .danger, + .rst-content .error, + .rst-content .hint, + .rst-content .important, + .rst-content .tip, + .rst-content .warning, + .rst-content .seealso, + .rst-content .admonition-todo, + .rst-content .admonition { + background-image: initial; + background-color: rgb(32, 35, 36); + } + + .wy-alert-title, + .rst-content .admonition-title { + color: rgb(232, 230, 227); + background-image: initial; + background-color: rgb(29, 91, 131); + } + + .wy-alert.wy-alert-danger, + .rst-content .wy-alert-danger.note, + .rst-content .wy-alert-danger.attention, + .rst-content .wy-alert-danger.caution, + .rst-content .danger, + .rst-content .error, + .rst-content .wy-alert-danger.hint, + .rst-content .wy-alert-danger.important, + .rst-content .wy-alert-danger.tip, + .rst-content .wy-alert-danger.warning, + .rst-content .wy-alert-danger.seealso, + .rst-content .wy-alert-danger.admonition-todo, + .rst-content .wy-alert-danger.admonition { + background-image: initial; + background-color: rgb(52, 12, 8); + } + + .wy-alert.wy-alert-danger .wy-alert-title, + .rst-content .wy-alert-danger.note .wy-alert-title, + .rst-content .wy-alert-danger.attention .wy-alert-title, + .rst-content .wy-alert-danger.caution .wy-alert-title, + .rst-content .danger .wy-alert-title, + .rst-content .error .wy-alert-title, + .rst-content .wy-alert-danger.hint .wy-alert-title, + .rst-content .wy-alert-danger.important .wy-alert-title, + .rst-content .wy-alert-danger.tip .wy-alert-title, + .rst-content .wy-alert-danger.warning .wy-alert-title, + .rst-content .wy-alert-danger.seealso .wy-alert-title, + .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, + .rst-content .wy-alert-danger.admonition .wy-alert-title, + .wy-alert.wy-alert-danger .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-danger .admonition-title, + .rst-content .wy-alert-danger.note .admonition-title, + .rst-content .wy-alert-danger.attention .admonition-title, + .rst-content .wy-alert-danger.caution .admonition-title, + .rst-content .danger .admonition-title, + .rst-content .error .admonition-title, + .rst-content .wy-alert-danger.hint .admonition-title, + .rst-content .wy-alert-danger.important .admonition-title, + .rst-content .wy-alert-danger.tip .admonition-title, + .rst-content .wy-alert-danger.warning .admonition-title, + .rst-content .wy-alert-danger.seealso .admonition-title, + .rst-content .wy-alert-danger.admonition-todo .admonition-title, + .rst-content .wy-alert-danger.admonition .admonition-title { + background-image: initial; + background-color: rgb(108, 22, 13); + } + + .wy-alert.wy-alert-warning, + .rst-content .wy-alert-warning.note, + .rst-content .attention, + .rst-content .caution, + .rst-content .wy-alert-warning.danger, + .rst-content .wy-alert-warning.error, + .rst-content .wy-alert-warning.hint, + .rst-content .wy-alert-warning.important, + .rst-content .wy-alert-warning.tip, + .rst-content .warning, + .rst-content .wy-alert-warning.seealso, + .rst-content .admonition-todo, + .rst-content .wy-alert-warning.admonition { + background-image: initial; + background-color: rgb(82, 53, 0); + } + + .wy-alert.wy-alert-warning .wy-alert-title, + .rst-content .wy-alert-warning.note .wy-alert-title, + .rst-content .attention .wy-alert-title, + .rst-content .caution .wy-alert-title, + .rst-content .wy-alert-warning.danger .wy-alert-title, + .rst-content .wy-alert-warning.error .wy-alert-title, + .rst-content .wy-alert-warning.hint .wy-alert-title, + .rst-content .wy-alert-warning.important .wy-alert-title, + .rst-content .wy-alert-warning.tip .wy-alert-title, + .rst-content .warning .wy-alert-title, + .rst-content .wy-alert-warning.seealso .wy-alert-title, + .rst-content .admonition-todo .wy-alert-title, + .rst-content .wy-alert-warning.admonition .wy-alert-title, + .wy-alert.wy-alert-warning .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-warning .admonition-title, + .rst-content .wy-alert-warning.note .admonition-title, + .rst-content .attention .admonition-title, + .rst-content .caution .admonition-title, + .rst-content .wy-alert-warning.danger .admonition-title, + .rst-content .wy-alert-warning.error .admonition-title, + .rst-content .wy-alert-warning.hint .admonition-title, + .rst-content .wy-alert-warning.important .admonition-title, + .rst-content .wy-alert-warning.tip .admonition-title, + .rst-content .warning .admonition-title, + .rst-content .wy-alert-warning.seealso .admonition-title, + .rst-content .admonition-todo .admonition-title, + .rst-content .wy-alert-warning.admonition .admonition-title { + background-image: initial; + background-color: rgb(123, 65, 14); + } + + .wy-alert.wy-alert-info, + .rst-content .note, + .rst-content .wy-alert-info.attention, + .rst-content .wy-alert-info.caution, + .rst-content .wy-alert-info.danger, + .rst-content .wy-alert-info.error, + .rst-content .wy-alert-info.hint, + .rst-content .wy-alert-info.important, + .rst-content .wy-alert-info.tip, + .rst-content .wy-alert-info.warning, + .rst-content .seealso, + .rst-content .wy-alert-info.admonition-todo, + .rst-content .wy-alert-info.admonition { + background-image: initial; + background-color: rgb(32, 35, 36); + } + + .wy-alert.wy-alert-info .wy-alert-title, + .rst-content .note .wy-alert-title, + .rst-content .wy-alert-info.attention .wy-alert-title, + .rst-content .wy-alert-info.caution .wy-alert-title, + .rst-content .wy-alert-info.danger .wy-alert-title, + .rst-content .wy-alert-info.error .wy-alert-title, + .rst-content .wy-alert-info.hint .wy-alert-title, + .rst-content .wy-alert-info.important .wy-alert-title, + .rst-content .wy-alert-info.tip .wy-alert-title, + .rst-content .wy-alert-info.warning .wy-alert-title, + .rst-content .seealso .wy-alert-title, + .rst-content .wy-alert-info.admonition-todo .wy-alert-title, + .rst-content .wy-alert-info.admonition .wy-alert-title, + .wy-alert.wy-alert-info .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-info .admonition-title, + .rst-content .note .admonition-title, + .rst-content .wy-alert-info.attention .admonition-title, + .rst-content .wy-alert-info.caution .admonition-title, + .rst-content .wy-alert-info.danger .admonition-title, + .rst-content .wy-alert-info.error .admonition-title, + .rst-content .wy-alert-info.hint .admonition-title, + .rst-content .wy-alert-info.important .admonition-title, + .rst-content .wy-alert-info.tip .admonition-title, + .rst-content .wy-alert-info.warning .admonition-title, + .rst-content .seealso .admonition-title, + .rst-content .wy-alert-info.admonition-todo .admonition-title, + .rst-content .wy-alert-info.admonition .admonition-title { + background-image: initial; + background-color: rgb(29, 91, 131); + } + + .wy-alert.wy-alert-success, + .rst-content .wy-alert-success.note, + .rst-content .wy-alert-success.attention, + .rst-content .wy-alert-success.caution, + .rst-content .wy-alert-success.danger, + .rst-content .wy-alert-success.error, + .rst-content .hint, + .rst-content .important, + .rst-content .tip, + .rst-content .wy-alert-success.warning, + .rst-content .wy-alert-success.seealso, + .rst-content .wy-alert-success.admonition-todo, + .rst-content .wy-alert-success.admonition { + background-image: initial; + background-color: rgb(9, 66, 58); + } + + .wy-alert.wy-alert-success .wy-alert-title, + .rst-content .wy-alert-success.note .wy-alert-title, + .rst-content .wy-alert-success.attention .wy-alert-title, + .rst-content .wy-alert-success.caution .wy-alert-title, + .rst-content .wy-alert-success.danger .wy-alert-title, + .rst-content .wy-alert-success.error .wy-alert-title, + .rst-content .hint .wy-alert-title, + .rst-content .important .wy-alert-title, + .rst-content .tip .wy-alert-title, + .rst-content .wy-alert-success.warning .wy-alert-title, + .rst-content .wy-alert-success.seealso .wy-alert-title, + .rst-content .wy-alert-success.admonition-todo .wy-alert-title, + .rst-content .wy-alert-success.admonition .wy-alert-title, + .wy-alert.wy-alert-success .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-success .admonition-title, + .rst-content .wy-alert-success.note .admonition-title, + .rst-content .wy-alert-success.attention .admonition-title, + .rst-content .wy-alert-success.caution .admonition-title, + .rst-content .wy-alert-success.danger .admonition-title, + .rst-content .wy-alert-success.error .admonition-title, + .rst-content .hint .admonition-title, + .rst-content .important .admonition-title, + .rst-content .tip .admonition-title, + .rst-content .wy-alert-success.warning .admonition-title, + .rst-content .wy-alert-success.seealso .admonition-title, + .rst-content .wy-alert-success.admonition-todo .admonition-title, + .rst-content .wy-alert-success.admonition .admonition-title { + background-image: initial; + background-color: rgb(21, 150, 125); + } + + .wy-alert.wy-alert-neutral, + .rst-content .wy-alert-neutral.note, + .rst-content .wy-alert-neutral.attention, + .rst-content .wy-alert-neutral.caution, + .rst-content .wy-alert-neutral.danger, + .rst-content .wy-alert-neutral.error, + .rst-content .wy-alert-neutral.hint, + .rst-content .wy-alert-neutral.important, + .rst-content .wy-alert-neutral.tip, + .rst-content .wy-alert-neutral.warning, + .rst-content .wy-alert-neutral.seealso, + .rst-content .wy-alert-neutral.admonition-todo, + .rst-content .wy-alert-neutral.admonition { + background-image: initial; + background-color: rgb(27, 36, 36); + } + + .wy-alert.wy-alert-neutral .wy-alert-title, + .rst-content .wy-alert-neutral.note .wy-alert-title, + .rst-content .wy-alert-neutral.attention .wy-alert-title, + .rst-content .wy-alert-neutral.caution .wy-alert-title, + .rst-content .wy-alert-neutral.danger .wy-alert-title, + .rst-content .wy-alert-neutral.error .wy-alert-title, + .rst-content .wy-alert-neutral.hint .wy-alert-title, + .rst-content .wy-alert-neutral.important .wy-alert-title, + .rst-content .wy-alert-neutral.tip .wy-alert-title, + .rst-content .wy-alert-neutral.warning .wy-alert-title, + .rst-content .wy-alert-neutral.seealso .wy-alert-title, + .rst-content .wy-alert-neutral.admonition-todo .wy-alert-title, + .rst-content .wy-alert-neutral.admonition .wy-alert-title, + .wy-alert.wy-alert-neutral .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-neutral .admonition-title, + .rst-content .wy-alert-neutral.note .admonition-title, + .rst-content .wy-alert-neutral.attention .admonition-title, + .rst-content .wy-alert-neutral.caution .admonition-title, + .rst-content .wy-alert-neutral.danger .admonition-title, + .rst-content .wy-alert-neutral.error .admonition-title, + .rst-content .wy-alert-neutral.hint .admonition-title, + .rst-content .wy-alert-neutral.important .admonition-title, + .rst-content .wy-alert-neutral.tip .admonition-title, + .rst-content .wy-alert-neutral.warning .admonition-title, + .rst-content .wy-alert-neutral.seealso .admonition-title, + .rst-content .wy-alert-neutral.admonition-todo .admonition-title, + .rst-content .wy-alert-neutral.admonition .admonition-title { + color: rgb(192, 186, 178); + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-alert.wy-alert-neutral a, + .rst-content .wy-alert-neutral.note a, + .rst-content .wy-alert-neutral.attention a, + .rst-content .wy-alert-neutral.caution a, + .rst-content .wy-alert-neutral.danger a, + .rst-content .wy-alert-neutral.error a, + .rst-content .wy-alert-neutral.hint a, + .rst-content .wy-alert-neutral.important a, + .rst-content .wy-alert-neutral.tip a, + .rst-content .wy-alert-neutral.warning a, + .rst-content .wy-alert-neutral.seealso a, + .rst-content .wy-alert-neutral.admonition-todo a, + .rst-content .wy-alert-neutral.admonition a { + color: rgb(84, 164, 217); + } + + .wy-tray-container li { + background-image: initial; + background-color: transparent; + color: rgb(232, 230, 227); + box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px 0px; + } + + .wy-tray-container li.wy-tray-item-success { + background-image: initial; + background-color: rgb(31, 139, 77); + } + + .wy-tray-container li.wy-tray-item-info { + background-image: initial; + background-color: rgb(33, 102, 148); + } + + .wy-tray-container li.wy-tray-item-warning { + background-image: initial; + background-color: rgb(178, 94, 20); + } + + .wy-tray-container li.wy-tray-item-danger { + background-image: initial; + background-color: rgb(162, 33, 20); + } + + .btn { + color: rgb(232, 230, 227); + border-color: rgba(140, 130, 115, 0.1); + background-color: rgb(31, 139, 77); + text-decoration-color: initial; + box-shadow: rgba(24, 26, 27, 0.5) 0px 1px 2px -1px inset, + rgba(0, 0, 0, 0.1) 0px -2px 0px 0px inset; + } + + .btn-hover { + background-image: initial; + background-color: rgb(37, 114, 165); + color: rgb(232, 230, 227); + } + + .btn:hover { + background-image: initial; + background-color: rgb(35, 156, 86); + color: rgb(232, 230, 227); + } + + .btn:focus { + background-image: initial; + background-color: rgb(35, 156, 86); + outline-color: initial; + } + + .btn:active { + box-shadow: rgba(0, 0, 0, 0.05) 0px -1px 0px 0px inset, + rgba(0, 0, 0, 0.1) 0px 2px 0px 0px inset; + } + + .btn:visited { + color: rgb(232, 230, 227); + } + + .btn:disabled { + background-image: none; + box-shadow: none; + } + + .btn-disabled { + background-image: none; + box-shadow: none; + } + + .btn-disabled:hover, + .btn-disabled:focus, + .btn-disabled:active { + background-image: none; + box-shadow: none; + } + + .btn-info { + background-color: rgb(33, 102, 148) !important; + } + + .btn-info:hover { + background-color: rgb(37, 114, 165) !important; + } + + .btn-neutral { + background-color: rgb(27, 36, 36) !important; + color: rgb(192, 186, 178) !important; + } + + .btn-neutral:hover { + color: rgb(192, 186, 178); + background-color: rgb(34, 44, 44) !important; + } + + .btn-neutral:visited { + color: rgb(192, 186, 178) !important; + } + + .btn-success { + background-color: rgb(31, 139, 77) !important; + } + + .btn-success:hover { + background-color: rgb(27, 122, 68) !important; + } + + .btn-danger { + background-color: rgb(162, 33, 20) !important; + } + + .btn-danger:hover { + background-color: rgb(149, 30, 18) !important; + } + + .btn-warning { + background-color: rgb(178, 94, 20) !important; + } + + .btn-warning:hover { + background-color: rgb(165, 87, 18) !important; + } + + .btn-invert { + background-color: rgb(26, 28, 29); + } + + .btn-invert:hover { + background-color: rgb(35, 38, 40) !important; + } + + .btn-link { + color: rgb(84, 164, 217); + box-shadow: none; + background-color: transparent !important; + border-color: transparent !important; + } + + .btn-link:hover { + box-shadow: none; + background-color: transparent !important; + color: rgb(79, 162, 216) !important; + } + + .btn-link:active { + box-shadow: none; + background-color: transparent !important; + color: rgb(79, 162, 216) !important; + } + + .btn-link:visited { + color: rgb(164, 103, 188); + } + + .wy-dropdown-menu { + background-image: initial; + background-color: rgb(26, 28, 29); + border-color: rgb(60, 65, 67); + box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 2px 0px; + } + + .wy-dropdown-menu > dd > a { + color: rgb(192, 186, 178); + } + + .wy-dropdown-menu > dd > a:hover { + background-image: initial; + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-dropdown-menu > dd.divider { + border-top-color: rgb(60, 65, 67); + } + + .wy-dropdown-menu > dd.call-to-action { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-dropdown-menu > dd.call-to-action:hover { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-dropdown-menu > dd.call-to-action .btn { + color: rgb(232, 230, 227); + } + + .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover { + background-image: initial; + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-dropdown-arrow::before { + border-bottom-color: rgb(51, 55, 57); + border-left-color: transparent; + border-right-color: transparent; + } + + fieldset { + border-color: initial; + } + + legend { + border-color: initial; + } + + label { + color: rgb(200, 195, 188); + } + + .wy-control-group.wy-control-group-required > label::after { + color: rgb(233, 88, 73); + } + + .wy-form-message-inline { + color: rgb(168, 160, 149); + } + + .wy-form-message { + color: rgb(168, 160, 149); + } + + input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"] { + border-color: rgb(62, 68, 70); + box-shadow: rgb(43, 47, 49) 0px 1px 3px inset; + } + + input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus { + outline-color: initial; + border-color: rgb(123, 114, 101); + } + + input.no-focus:focus { + border-color: rgb(62, 68, 70) !important; + } + + input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { + outline-color: rgb(13, 113, 167); + } + + input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled] { + background-color: rgb(27, 29, 30); + } + + input:focus:invalid, + textarea:focus:invalid, + select:focus:invalid { + color: rgb(233, 88, 73); + border-color: rgb(149, 31, 18); + } + + input:focus:invalid:focus, + textarea:focus:invalid:focus, + select:focus:invalid:focus { + border-color: rgb(149, 31, 18); + } + + input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus { + outline-color: rgb(149, 31, 18); + } + + select, + textarea { + border-color: rgb(62, 68, 70); + box-shadow: rgb(43, 47, 49) 0px 1px 3px inset; + } + + select { + border-color: rgb(62, 68, 70); + background-color: rgb(24, 26, 27); + } + + select:focus, + textarea:focus { + outline-color: initial; + } + + select[disabled], + textarea[disabled], + input[readonly], + select[readonly], + textarea[readonly] { + background-color: rgb(27, 29, 30); + } + + .wy-checkbox, + .wy-radio { + color: rgb(192, 186, 178); + } + + .wy-input-prefix .wy-input-context, + .wy-input-suffix .wy-input-context { + background-color: rgb(27, 36, 36); + border-color: rgb(62, 68, 70); + color: rgb(168, 160, 149); + } + + .wy-input-suffix .wy-input-context { + border-left-color: initial; + } + + .wy-input-prefix .wy-input-context { + border-right-color: initial; + } + + .wy-switch::before { + background-image: initial; + background-color: rgb(53, 57, 59); + } + + .wy-switch::after { + background-image: initial; + background-color: rgb(82, 88, 92); + } + + .wy-switch span { + color: rgb(200, 195, 188); + } + + .wy-switch.active::before { + background-image: initial; + background-color: rgb(24, 106, 58); + } + + .wy-switch.active::after { + background-image: initial; + background-color: rgb(31, 139, 77); + } + + .wy-control-group.wy-control-group-error .wy-form-message, + .wy-control-group.wy-control-group-error > label { + color: rgb(233, 88, 73); + } + + .wy-control-group.wy-control-group-error input[type="text"], .wy-control-group.wy-control-group-error input[type="password"], .wy-control-group.wy-control-group-error input[type="email"], .wy-control-group.wy-control-group-error input[type="url"], .wy-control-group.wy-control-group-error input[type="date"], .wy-control-group.wy-control-group-error input[type="month"], .wy-control-group.wy-control-group-error input[type="time"], .wy-control-group.wy-control-group-error input[type="datetime"], .wy-control-group.wy-control-group-error input[type="datetime-local"], .wy-control-group.wy-control-group-error input[type="week"], .wy-control-group.wy-control-group-error input[type="number"], .wy-control-group.wy-control-group-error input[type="search"], .wy-control-group.wy-control-group-error input[type="tel"], .wy-control-group.wy-control-group-error input[type="color"] { + border-color: rgb(149, 31, 18); + } + + .wy-control-group.wy-control-group-error textarea { + border-color: rgb(149, 31, 18); + } + + .wy-inline-validate.wy-inline-validate-success .wy-input-context { + color: rgb(92, 218, 145); + } + + .wy-inline-validate.wy-inline-validate-danger .wy-input-context { + color: rgb(233, 88, 73); + } + + .wy-inline-validate.wy-inline-validate-warning .wy-input-context { + color: rgb(232, 138, 54); + } + + .wy-inline-validate.wy-inline-validate-info .wy-input-context { + color: rgb(84, 164, 217); + } + + .wy-table caption, + .rst-content table.docutils caption, + .rst-content table.field-list caption { + color: rgb(232, 230, 227); + } + + .wy-table thead, + .rst-content table.docutils thead, + .rst-content table.field-list thead { + color: rgb(232, 230, 227); + } + + .wy-table thead th, + .rst-content table.docutils thead th, + .rst-content table.field-list thead th { + border-bottom-color: rgb(56, 61, 63); + } + + .wy-table td, + .rst-content table.docutils td, + .rst-content table.field-list td { + background-color: transparent; + } + + .wy-table-secondary { + color: rgb(152, 143, 129); + } + + .wy-table-tertiary { + color: rgb(152, 143, 129); + } + + .wy-table-odd td, + .wy-table-striped tr:nth-child(2n-1) td, + .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color: rgb(27, 36, 36); + } + + .wy-table-backed { + background-color: rgb(27, 36, 36); + } + + .wy-table-bordered-all, + .rst-content table.docutils { + border-color: rgb(56, 61, 63); + } + + .wy-table-bordered-all td, + .rst-content table.docutils td { + border-bottom-color: rgb(56, 61, 63); + border-left-color: rgb(56, 61, 63); + } + + .wy-table-bordered { + border-color: rgb(56, 61, 63); + } + + .wy-table-bordered-rows td { + border-bottom-color: rgb(56, 61, 63); + } + + .wy-table-horizontal td, + .wy-table-horizontal th { + border-bottom-color: rgb(56, 61, 63); + } + + a { + color: rgb(84, 164, 217); + text-decoration-color: initial; + } + + a:hover { + color: rgb(68, 156, 214); + } + + a:visited { + color: rgb(164, 103, 188); + } + + body { + color: rgb(192, 186, 178); + background-image: initial; + background-color: rgb(33, 35, 37); + } + + .wy-text-strike { + text-decoration-color: initial; + } + + .wy-text-warning { + color: rgb(232, 138, 54) !important; + } + + a.wy-text-warning:hover { + color: rgb(236, 157, 87) !important; + } + + .wy-text-info { + color: rgb(84, 164, 217) !important; + } + + a.wy-text-info:hover { + color: rgb(79, 162, 216) !important; + } + + .wy-text-success { + color: rgb(92, 218, 145) !important; + } + + a.wy-text-success:hover { + color: rgb(73, 214, 133) !important; + } + + .wy-text-danger { + color: rgb(233, 88, 73) !important; + } + + a.wy-text-danger:hover { + color: rgb(237, 118, 104) !important; + } + + .wy-text-neutral { + color: rgb(192, 186, 178) !important; + } + + a.wy-text-neutral:hover { + color: rgb(176, 169, 159) !important; + } + + hr { + border-right-color: initial; + border-bottom-color: initial; + border-left-color: initial; + border-top-color: rgb(56, 61, 63); + } + + code, + .rst-content tt, + .rst-content code { + background-image: initial; + background-color: rgb(24, 26, 27); + border-color: rgb(56, 61, 63); + color: rgb(233, 88, 73); + } + + .wy-plain-list-disc, + .rst-content .section ul, + .rst-content .toctree-wrapper ul, + article ul { + list-style-image: initial; + } + + .wy-plain-list-disc li, + .rst-content .section ul li, + .rst-content .toctree-wrapper ul li, + article ul li { + list-style-image: initial; + } + + .wy-plain-list-disc li li, + .rst-content .section ul li li, + .rst-content .toctree-wrapper ul li li, + article ul li li { + list-style-image: initial; + } + + .wy-plain-list-disc li li li, + .rst-content .section ul li li li, + .rst-content .toctree-wrapper ul li li li, + article ul li li li { + list-style-image: initial; + } + + .wy-plain-list-disc li ol li, + .rst-content .section ul li ol li, + .rst-content .toctree-wrapper ul li ol li, + article ul li ol li { + list-style-image: initial; + } + + .wy-plain-list-decimal, + .rst-content .section ol, + .rst-content ol.arabic, + article ol { + list-style-image: initial; + } + + .wy-plain-list-decimal li, + .rst-content .section ol li, + .rst-content ol.arabic li, + article ol li { + list-style-image: initial; + } + + .wy-plain-list-decimal li ul li, + .rst-content .section ol li ul li, + .rst-content ol.arabic li ul li, + article ol li ul li { + list-style-image: initial; + } + + .wy-breadcrumbs li code, + .wy-breadcrumbs li .rst-content tt, + .rst-content .wy-breadcrumbs li tt { + border-color: initial; + background-image: none; + background-color: initial; + } + + .wy-breadcrumbs li code.literal, + .wy-breadcrumbs li .rst-content tt.literal, + .rst-content .wy-breadcrumbs li tt.literal { + color: rgb(192, 186, 178); + } + + .wy-breadcrumbs-extra { + color: rgb(184, 178, 169); + } + + .wy-menu a:hover { + text-decoration-color: initial; + } + + .wy-menu-horiz li:hover { + background-image: initial; + background-color: rgba(24, 26, 27, 0.1); + } + + .wy-menu-horiz li.divide-left { + border-left-color: rgb(119, 110, 98); + } + + .wy-menu-horiz li.divide-right { + border-right-color: rgb(119, 110, 98); + } + + .wy-menu-vertical header, + .wy-menu-vertical p.caption { + color: rgb(99, 161, 201); + } + + .wy-menu-vertical li.divide-top { + border-top-color: rgb(119, 110, 98); + } + + .wy-menu-vertical li.divide-bottom { + border-bottom-color: rgb(119, 110, 98); + } + + .wy-menu-vertical li.current { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-menu-vertical li.current a { + color: rgb(152, 143, 129); + border-right-color: rgb(63, 69, 71); + } + + .wy-menu-vertical li.current a:hover { + background-image: initial; + background-color: rgb(47, 51, 53); + } + + .wy-menu-vertical li code, + .wy-menu-vertical li .rst-content tt, + .rst-content .wy-menu-vertical li tt { + border-color: initial; + background-image: inherit; + background-color: inherit; + color: inherit; + } + + .wy-menu-vertical li span.toctree-expand { + color: rgb(183, 177, 168); + } + + .wy-menu-vertical li.on a, + .wy-menu-vertical li.current > a { + color: rgb(192, 186, 178); + background-image: initial; + background-color: rgb(26, 28, 29); + border-color: initial; + } + + .wy-menu-vertical li.on a:hover, + .wy-menu-vertical li.current > a:hover { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + .wy-menu-vertical li.on a:hover span.toctree-expand, + .wy-menu-vertical li.current > a:hover span.toctree-expand { + color: rgb(152, 143, 129); + } + + .wy-menu-vertical li.on a span.toctree-expand, + .wy-menu-vertical li.current > a span.toctree-expand { + color: rgb(200, 195, 188); + } + + .wy-menu-vertical li.toctree-l1.current > a { + border-bottom-color: rgb(63, 69, 71); + border-top-color: rgb(63, 69, 71); + } + + .wy-menu-vertical li.toctree-l2 a, + .wy-menu-vertical li.toctree-l3 a, + .wy-menu-vertical li.toctree-l4 a { + color: rgb(192, 186, 178); + } + + .wy-menu-vertical li.toctree-l2.current > a { + background-image: initial; + background-color: rgb(54, 59, 61); + } + + .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a { + background-image: initial; + background-color: rgb(54, 59, 61); + } + + .wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand { + color: rgb(152, 143, 129); + } + + .wy-menu-vertical li.toctree-l2 span.toctree-expand { + color: rgb(174, 167, 156); + } + + .wy-menu-vertical li.toctree-l3.current > a { + background-image: initial; + background-color: rgb(61, 66, 69); + } + + .wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { + background-image: initial; + background-color: rgb(61, 66, 69); + } + + .wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand { + color: rgb(152, 143, 129); + } + + .wy-menu-vertical li.toctree-l3 span.toctree-expand { + color: rgb(166, 158, 146); + } + + .wy-menu-vertical li.toctree-l2.current a, + .wy-menu-vertical li.toctree-l3.current a { + background-color: #363636; + } + + .wy-menu-vertical li ul li a { + color: rgb(208, 204, 198); + } + + .wy-menu-vertical a { + color: rgb(208, 204, 198); + } + + .wy-menu-vertical a:hover { + background-color: rgb(57, 62, 64); + } + + .wy-menu-vertical a:hover span.toctree-expand { + color: rgb(208, 204, 198); + } + + .wy-menu-vertical a:active { + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-menu-vertical a:active span.toctree-expand { + color: rgb(232, 230, 227); + } + + .wy-side-nav-search { + background-color: rgb(33, 102, 148); + color: rgb(230, 228, 225); + } + + .wy-side-nav-search input[type="text"] { + border-color: rgb(35, 111, 160); + } + + .wy-side-nav-search img { + background-color: rgb(33, 102, 148); + } + + .wy-side-nav-search > a, + .wy-side-nav-search .wy-dropdown > a { + color: rgb(230, 228, 225); + } + + .wy-side-nav-search > a:hover, + .wy-side-nav-search .wy-dropdown > a:hover { + background-image: initial; + background-color: rgba(24, 26, 27, 0.1); + } + + .wy-side-nav-search > a img.logo, + .wy-side-nav-search .wy-dropdown > a img.logo { + background-image: initial; + background-color: transparent; + } + + .wy-side-nav-search > div.version { + color: rgba(232, 230, 227, 0.3); + } + + .wy-nav .wy-menu-vertical header { + color: rgb(84, 164, 217); + } + + .wy-nav .wy-menu-vertical a { + color: rgb(184, 178, 169); + } + + .wy-nav .wy-menu-vertical a:hover { + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-body-for-nav { + background-image: initial; + background-color: rgb(24, 26, 27); + } + + .wy-nav-side { + color: rgb(169, 161, 150); + background-image: initial; + background-color: rgb(38, 41, 43); + } + + .wy-nav-top { + background-image: initial; + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-nav-top a { + color: rgb(232, 230, 227); + } + + .wy-nav-top img { + background-color: rgb(33, 102, 148); + } + + .wy-nav-content-wrap { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + .wy-body-mask { + background-image: initial; + background-color: rgba(0, 0, 0, 0.2); + } + + footer { + color: rgb(152, 143, 129); + } + + footer span.commit code, + footer span.commit .rst-content tt, + .rst-content footer span.commit tt { + background-image: none; + background-color: initial; + border-color: initial; + color: rgb(152, 143, 129); + } + + #search-results .search li { + border-bottom-color: rgb(56, 61, 63); + } + + #search-results .search li:first-child { + border-top-color: rgb(56, 61, 63); + } + + #search-results .context { + color: rgb(152, 143, 129); + } + + @media screen and (min-width: 1100px) { + .wy-nav-content-wrap { + background-image: initial; + background-color: rgba(0, 0, 0, 0.05); + } + + .wy-nav-content { + background-image: initial; + background-color: rgb(26, 28, 29); + } + } + .rst-versions { + color: rgb(230, 228, 225); + background-image: initial; + background-color: rgb(23, 24, 25); + } + + .rst-versions a { + color: rgb(84, 164, 217); + text-decoration-color: initial; + } + + .rst-versions .rst-current-version { + background-color: rgb(29, 31, 32); + color: rgb(92, 218, 145); + } + + .rst-versions .rst-current-version .fa, + .rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand, + .wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand, + .rst-versions .rst-current-version .rst-content .admonition-title, + .rst-content .rst-versions .rst-current-version .admonition-title, + .rst-versions .rst-current-version .rst-content h1 .headerlink, + .rst-content h1 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h2 .headerlink, + .rst-content h2 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h3 .headerlink, + .rst-content h3 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h4 .headerlink, + .rst-content h4 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h5 .headerlink, + .rst-content h5 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h6 .headerlink, + .rst-content h6 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content dl dt .headerlink, + .rst-content dl dt .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content p.caption .headerlink, + .rst-content p.caption .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content table > caption .headerlink, + .rst-content table > caption .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content .code-block-caption .headerlink, + .rst-content .code-block-caption .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content tt.download span:first-child, + .rst-content tt.download .rst-versions .rst-current-version span:first-child, + .rst-versions .rst-current-version .rst-content code.download span:first-child, + .rst-content code.download .rst-versions .rst-current-version span:first-child, + .rst-versions .rst-current-version .icon { + color: rgb(230, 228, 225); + } + + .rst-versions .rst-current-version.rst-out-of-date { + background-color: rgb(162, 33, 20); + color: rgb(232, 230, 227); + } + + .rst-versions .rst-current-version.rst-active-old-version { + background-color: rgb(192, 156, 11); + color: rgb(232, 230, 227); + } + + .rst-versions .rst-other-versions { + color: rgb(152, 143, 129); + } + + .rst-versions .rst-other-versions hr { + border-right-color: initial; + border-bottom-color: initial; + border-left-color: initial; + border-top-color: rgb(119, 111, 98); + } + + .rst-versions .rst-other-versions dd a { + color: rgb(230, 228, 225); + } + + .rst-versions.rst-badge { + border-color: initial; + } + + .rst-content abbr[title] { + text-decoration-color: initial; + } + + .rst-content.style-external-links a.reference.external::after { + color: rgb(184, 178, 169); + } + + .rst-content pre.literal-block, .rst-content div[class^="highlight"] { + border-color: rgb(56, 61, 63); + } + + .rst-content pre.literal-block div[class^="highlight"], .rst-content div[class^="highlight"] div[class^="highlight"] { + border-color: initial; + } + + .rst-content .linenodiv pre { + border-right-color: rgb(54, 59, 61); + } + + .rst-content .admonition table { + border-color: rgba(140, 130, 115, 0.1); + } + + .rst-content .admonition table td, + .rst-content .admonition table th { + background-image: initial !important; + background-color: transparent !important; + border-color: rgba(140, 130, 115, 0.1) !important; + } + + .rst-content .section ol.loweralpha, + .rst-content .section ol.loweralpha li { + list-style-image: initial; + } + + .rst-content .section ol.upperalpha, + .rst-content .section ol.upperalpha li { + list-style-image: initial; + } + + .rst-content .toc-backref { + color: rgb(192, 186, 178); + } + + .rst-content .sidebar { + background-image: initial; + background-color: rgb(27, 36, 36); + border-color: rgb(56, 61, 63); + } + + .rst-content .sidebar .sidebar-title { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .rst-content .highlighted { + background-image: initial; + background-color: rgb(192, 156, 11); + } + + .rst-content table.docutils.citation, + .rst-content table.docutils.footnote { + background-image: none; + background-color: initial; + border-color: initial; + color: rgb(152, 143, 129); + } + + .rst-content table.docutils.citation td, + .rst-content table.docutils.citation tr, + .rst-content table.docutils.footnote td, + .rst-content table.docutils.footnote tr { + border-color: initial; + background-color: transparent !important; + } + + .rst-content table.docutils.citation tt, + .rst-content table.docutils.citation code, + .rst-content table.docutils.footnote tt, + .rst-content table.docutils.footnote code { + color: rgb(178, 172, 162); + } + + .rst-content table.docutils th { + border-color: rgb(56, 61, 63); + } + + .rst-content table.field-list { + border-color: initial; + } + + .rst-content table.field-list td { + border-color: initial; + } + + .rst-content tt, + .rst-content tt, + .rst-content code { + color: rgb(232, 230, 227); + } + + .rst-content tt.literal, + .rst-content tt.literal, + .rst-content code.literal { + color: rgb(233, 88, 73); + } + + .rst-content tt.xref, + a .rst-content tt, + .rst-content tt.xref, + .rst-content code.xref, + a .rst-content tt, + a .rst-content code { + color: rgb(192, 186, 178); + } + + .rst-content a tt, + .rst-content a tt, + .rst-content a code { + color: rgb(84, 164, 217); + } + + .rst-content dl:not(.docutils) dt { + background-image: initial; + background-color: rgb(32, 35, 36); + color: rgb(84, 164, 217); + border-top-color: rgb(28, 89, 128); + } + + .rst-content dl:not(.docutils) dt::before { + color: rgb(109, 178, 223); + } + + .rst-content dl:not(.docutils) dt .headerlink { + color: rgb(192, 186, 178); + } + + .rst-content dl:not(.docutils) dl dt { + border-top-color: initial; + border-right-color: initial; + border-bottom-color: initial; + border-left-color: rgb(62, 68, 70); + background-image: initial; + background-color: rgb(32, 35, 37); + color: rgb(178, 172, 162); + } + + .rst-content dl:not(.docutils) dl dt .headerlink { + color: rgb(192, 186, 178); + } + + .rst-content dl:not(.docutils) tt.descname, + .rst-content dl:not(.docutils) tt.descclassname, + .rst-content dl:not(.docutils) tt.descname, + .rst-content dl:not(.docutils) code.descname, + .rst-content dl:not(.docutils) tt.descclassname, + .rst-content dl:not(.docutils) code.descclassname { + background-color: transparent; + border-color: initial; + } + + .rst-content dl:not(.docutils) .optional { + color: rgb(232, 230, 227); + } + + .rst-content .viewcode-link, + .rst-content .viewcode-back { + color: rgb(92, 218, 145); + } + + .rst-content tt.download, + .rst-content code.download { + background-image: inherit; + background-color: inherit; + color: inherit; + border-color: inherit; + } + + .rst-content .guilabel { + border-color: rgb(27, 84, 122); + background-image: initial; + background-color: rgb(32, 35, 36); + } + + span[id*="MathJax-Span"] { + color: rgb(192, 186, 178); + } + + .highlight .hll { + background-color: rgb(82, 82, 0); + } + + .highlight { + background-image: initial; + background-color: rgb(61, 82, 0); + } + + .highlight .c { + color: rgb(119, 179, 195); + } + + .highlight .err { + border-color: rgb(179, 0, 0); + } + + .highlight .k { + color: rgb(126, 255, 163); + } + + .highlight .o { + color: rgb(168, 160, 149); + } + + .highlight .ch { + color: rgb(119, 179, 195); + } + + .highlight .cm { + color: rgb(119, 179, 195); + } + + .highlight .cp { + color: rgb(126, 255, 163); + } + + .highlight .cpf { + color: rgb(119, 179, 195); + } + + .highlight .c1 { + color: rgb(119, 179, 195); + } + + .highlight .cs { + color: rgb(119, 179, 195); + background-color: rgb(60, 0, 0); + } + + .highlight .gd { + color: rgb(255, 92, 92); + } + + .highlight .gr { + color: rgb(255, 26, 26); + } + + .highlight .gh { + color: rgb(127, 174, 255); + } + + .highlight .gi { + color: rgb(92, 255, 92); + } + + .highlight .go { + color: rgb(200, 195, 188); + } + + .highlight .gp { + color: rgb(246, 147, 68); + } + + .highlight .gu { + color: rgb(255, 114, 255); + } + + .highlight .gt { + color: rgb(71, 160, 255); + } + + .highlight .kc { + color: rgb(126, 255, 163); + } + + .highlight .kd { + color: rgb(126, 255, 163); + } + + .highlight .kn { + color: rgb(126, 255, 163); + } + + .highlight .kp { + color: rgb(126, 255, 163); + } + + .highlight .kr { + color: rgb(126, 255, 163); + } + + .highlight .kt { + color: rgb(255, 137, 103); + } + + .highlight .m { + color: rgb(125, 222, 174); + } + + .highlight .s { + color: rgb(123, 166, 202); + } + + .highlight .na { + color: rgb(123, 166, 202); + } + + .highlight .nb { + color: rgb(126, 255, 163); + } + + .highlight .nc { + color: rgb(81, 194, 242); + } + + .highlight .no { + color: rgb(103, 177, 215); + } + + .highlight .nd { + color: rgb(178, 172, 162); + } + + .highlight .ni { + color: rgb(217, 100, 73); + } + + .highlight .ne { + color: rgb(126, 255, 163); + } + + .highlight .nf { + color: rgb(131, 186, 249); + } + + .highlight .nl { + color: rgb(137, 193, 255); + } + + .highlight .nn { + color: rgb(81, 194, 242); + } + + .highlight .nt { + color: rgb(138, 191, 249); + } + + .highlight .nv { + color: rgb(190, 103, 215); + } + + .highlight .ow { + color: rgb(126, 255, 163); + } + + .highlight .w { + color: rgb(189, 183, 175); + } + + .highlight .mb { + color: rgb(125, 222, 174); + } + + .highlight .mf { + color: rgb(125, 222, 174); + } + + .highlight .mh { + color: rgb(125, 222, 174); + } + + .highlight .mi { + color: rgb(125, 222, 174); + } + + .highlight .mo { + color: rgb(125, 222, 174); + } + + .highlight .sa { + color: rgb(123, 166, 202); + } + + .highlight .sb { + color: rgb(123, 166, 202); + } + + .highlight .sc { + color: rgb(123, 166, 202); + } + + .highlight .dl { + color: rgb(123, 166, 202); + } + + .highlight .sd { + color: rgb(123, 166, 202); + } + + .highlight .s2 { + color: rgb(123, 166, 202); + } + + .highlight .se { + color: rgb(123, 166, 202); + } + + .highlight .sh { + color: rgb(123, 166, 202); + } + + .highlight .si { + color: rgb(117, 168, 209); + } + + .highlight .sx { + color: rgb(246, 147, 68); + } + + .highlight .sr { + color: rgb(133, 182, 224); + } + + .highlight .s1 { + color: rgb(123, 166, 202); + } + + .highlight .ss { + color: rgb(188, 230, 128); + } + + .highlight .bp { + color: rgb(126, 255, 163); + } + + .highlight .fm { + color: rgb(131, 186, 249); + } + + .highlight .vc { + color: rgb(190, 103, 215); + } + + .highlight .vg { + color: rgb(190, 103, 215); + } + + .highlight .vi { + color: rgb(190, 103, 215); + } + + .highlight .vm { + color: rgb(190, 103, 215); + } + + .highlight .il { + color: rgb(125, 222, 174); + } + + .rst-other-versions a { + border-color: initial; + } + + .ethical-sidebar .ethical-image-link, + .ethical-footer .ethical-image-link { + border-color: initial; + } + + .ethical-sidebar, + .ethical-footer { + background-color: rgb(34, 36, 38); + border-color: rgb(62, 68, 70); + color: rgb(226, 223, 219); + } + + .ethical-sidebar ul { + list-style-image: initial; + } + + .ethical-sidebar ul li { + background-color: rgb(5, 77, 121); + color: rgb(232, 230, 227); + } + + .ethical-sidebar a, + .ethical-sidebar a:visited, + .ethical-sidebar a:hover, + .ethical-sidebar a:active, + .ethical-footer a, + .ethical-footer a:visited, + .ethical-footer a:hover, + .ethical-footer a:active { + color: rgb(226, 223, 219); + text-decoration-color: initial !important; + border-bottom-color: initial !important; + } + + .ethical-callout a { + color: rgb(161, 153, 141) !important; + text-decoration-color: initial !important; + } + + .ethical-fixedfooter { + background-color: rgb(34, 36, 38); + border-top-color: rgb(66, 72, 74); + color: rgb(192, 186, 178); + } + + .ethical-fixedfooter .ethical-text::before { + background-color: rgb(61, 140, 64); + color: rgb(232, 230, 227); + } + + .ethical-fixedfooter .ethical-callout { + color: rgb(168, 160, 149); + } + + .ethical-fixedfooter a, + .ethical-fixedfooter a:hover, + .ethical-fixedfooter a:active, + .ethical-fixedfooter a:visited { + color: rgb(192, 186, 178); + text-decoration-color: initial; + } + + .ethical-rtd .ethical-sidebar { + color: rgb(184, 178, 169); + } + + .ethical-alabaster a.ethical-image-link { + border-color: initial !important; + } + + .ethical-dark-theme .ethical-sidebar { + background-color: rgb(58, 62, 65); + border-color: rgb(75, 81, 84); + color: rgb(193, 188, 180) !important; + } + + .ethical-dark-theme a, + .ethical-dark-theme a:visited { + color: rgb(216, 213, 208) !important; + border-bottom-color: initial !important; + } + + .ethical-dark-theme .ethical-callout a { + color: rgb(184, 178, 169) !important; + } + + .keep-us-sustainable { + border-color: rgb(87, 133, 38); + } + + .keep-us-sustainable a, + .keep-us-sustainable a:hover, + .keep-us-sustainable a:visited { + text-decoration-color: initial; + } + + .wy-body-for-nav .keep-us-sustainable { + color: rgb(184, 178, 169); + } + + .wy-body-for-nav .keep-us-sustainable a { + color: rgb(222, 219, 215); + } + + /* For black-on-white/transparent images at handbook/text-anchors.html */ + #text-anchors img { + filter: invert(1) brightness(0.85) hue-rotate(-60deg); + } +} diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/light.css b/_static/css/light.css new file mode 100644 index 00000000..04edd7b1 --- /dev/null +++ b/_static/css/light.css @@ -0,0 +1,8 @@ +@media (prefers-color-scheme: light) { + + .wy-menu-vertical li.toctree-l2.current a, + .wy-menu-vertical li.toctree-l3.current a { + background-color: #c9c9c9; + } + +} diff --git a/_static/css/styles.css b/_static/css/styles.css new file mode 100644 index 00000000..a1be0c0d --- /dev/null +++ b/_static/css/styles.css @@ -0,0 +1,14 @@ +th p { + margin-bottom: 0; +} + +.rst-content tr .line-block { + font-size: 1rem; + margin-bottom: 0; +} + +@media screen and (min-width: 1100px) { + .wy-nav-content { + max-width: 80% !important; + } +} diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 00000000..19a446a0 --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..4d67807d --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..08886833 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: 'latest', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 00000000..c4c6022f --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/html5shiv.min.js b/_static/js/html5shiv.min.js new file mode 100644 index 00000000..cd1c674f --- /dev/null +++ b/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/script.js b/_static/js/script.js new file mode 100644 index 00000000..5cb6494e --- /dev/null +++ b/_static/js/script.js @@ -0,0 +1,58 @@ +jQuery(document).ready(function ($) { + setTimeout(function () { + var sectionID = 'base'; + var search = function ($section, $sidebarItem) { + $section.children('.section, .function, .method').each(function () { + if ($(this).hasClass('section')) { + sectionID = $(this).attr('id'); + search($(this), $sidebarItem.parent().find('[href="#'+sectionID+'"]')); + } else { + var $dt = $(this).children('dt'); + var id = $dt.attr('id'); + if (id === undefined) { + return; + } + + var $functionsUL = $sidebarItem.siblings('[data-sectionID='+sectionID+']'); + if (!$functionsUL.length) { + $functionsUL = $('