diff --git a/.circleci/config.yml b/.circleci/config.yml
index 624e4eae..4eaf808f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -75,11 +75,11 @@ workflows:
matrix:
parameters:
python:
- - "3.8"
- - "3.9"
+ - "3.9.18"
- "3.10"
- "3.11"
- "3.12"
+ - "3.13"
- test_nooptionals:
matrix:
parameters:
@@ -89,4 +89,4 @@ workflows:
matrix:
parameters:
python:
- - "3.8"
+ - "3.9"
diff --git a/.github/workflows/github-pages.yaml b/.github/workflows/github-pages.yaml
new file mode 100644
index 00000000..621f2d73
--- /dev/null
+++ b/.github/workflows/github-pages.yaml
@@ -0,0 +1,79 @@
+name: Deploy Documentation to Github Pages
+
+on:
+ push:
+ branches:
+ - master
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+ actions: read
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: "pages"
+ cancel-in-progress: false
+
+# Default to bash
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ # Build job
+ build:
+ runs-on: ubuntu-latest
+ env:
+ HUGO_VERSION: 0.145.0
+ steps:
+ - name: Install Hugo CLI
+ run: |
+ wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
+ && sudo dpkg -i ${{ runner.temp }}/hugo.deb
+ #- name: Install Dart Sass
+ # run: sudo snap install dart-sass
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ - name: Setup Pages
+ id: pages
+ uses: actions/configure-pages@v5
+ - name: Install Node.js dependencies
+ run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
+ working-directory: ./docs
+ - name: Build with Hugo
+ env:
+ # For maximum backward compatibility with Hugo modules
+ HUGO_ENVIRONMENT: production
+ HUGO_ENV: production
+ run: |
+ hugo \
+ --gc \
+ --minify \
+ --baseURL "${{ steps.pages.outputs.base_url }}/"
+ working-directory: ./docs
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./docs/public
+
+ # Deployment job
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ needs: build
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index cad821dd..00000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,4 +0,0 @@
-graft tests
-global-exclude *.py[cod]
-prune __pycache__
-prune */__pycache__
diff --git a/README.md b/README.md
index 3aef0c4d..c9edd996 100644
--- a/README.md
+++ b/README.md
@@ -2,790 +2,17 @@
The official Python client for [Prometheus](https://prometheus.io).
-## Three Step Demo
-
-**One**: Install the client:
-```
-pip install prometheus-client
-```
-
-**Two**: Paste the following into a Python interpreter:
-```python
-from prometheus_client import start_http_server, Summary
-import random
-import time
-
-# Create a metric to track time spent and requests made.
-REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')
-
-# Decorate function with metric.
-@REQUEST_TIME.time()
-def process_request(t):
- """A dummy function that takes some time."""
- time.sleep(t)
-
-if __name__ == '__main__':
- # Start up the server to expose the metrics.
- start_http_server(8000)
- # Generate some requests.
- while True:
- process_request(random.random())
-```
-
-**Three**: Visit [http://localhost:8000/](http://localhost:8000/) to view the metrics.
-
-From one easy to use decorator you get:
- * `request_processing_seconds_count`: Number of times this function was called.
- * `request_processing_seconds_sum`: Total amount of time spent in this function.
-
-Prometheus's `rate` function allows calculation of both requests per second,
-and latency over time from this data.
-
-In addition if you're on Linux the `process` metrics expose CPU, memory and
-other information about the process for free!
-
## Installation
```
pip install prometheus-client
```
-This package can be found on
-[PyPI](https://pypi.python.org/pypi/prometheus_client).
-
-## Instrumenting
-
-Four types of metric are offered: Counter, Gauge, Summary and Histogram.
-See the documentation on [metric types](http://prometheus.io/docs/concepts/metric_types/)
-and [instrumentation best practices](https://prometheus.io/docs/practices/instrumentation/#counter-vs-gauge-summary-vs-histogram)
-on how to use them.
-
-### Counter
-
-Counters go up, and reset when the process restarts.
-
-
-```python
-from prometheus_client import Counter
-c = Counter('my_failures', 'Description of counter')
-c.inc() # Increment by 1
-c.inc(1.6) # Increment by given value
-```
-
-If there is a suffix of `_total` on the metric name, it will be removed. When
-exposing the time series for counter, a `_total` suffix will be added. This is
-for compatibility between OpenMetrics and the Prometheus text format, as OpenMetrics
-requires the `_total` suffix.
-
-There are utilities to count exceptions raised:
-
-```python
-@c.count_exceptions()
-def f():
- pass
-
-with c.count_exceptions():
- pass
-
-# Count only one type of exception
-with c.count_exceptions(ValueError):
- pass
-```
-
-### Gauge
-
-Gauges can go up and down.
-
-```python
-from prometheus_client import Gauge
-g = Gauge('my_inprogress_requests', 'Description of gauge')
-g.inc() # Increment by 1
-g.dec(10) # Decrement by given value
-g.set(4.2) # Set to a given value
-```
-
-There are utilities for common use cases:
-
-```python
-g.set_to_current_time() # Set to current unixtime
-
-# Increment when entered, decrement when exited.
-@g.track_inprogress()
-def f():
- pass
-
-with g.track_inprogress():
- pass
-```
-
-A Gauge can also take its value from a callback:
-
-```python
-d = Gauge('data_objects', 'Number of objects')
-my_dict = {}
-d.set_function(lambda: len(my_dict))
-```
-
-### Summary
-
-Summaries track the size and number of events.
-
-```python
-from prometheus_client import Summary
-s = Summary('request_latency_seconds', 'Description of summary')
-s.observe(4.7) # Observe 4.7 (seconds in this case)
-```
-
-There are utilities for timing code:
-
-```python
-@s.time()
-def f():
- pass
-
-with s.time():
- pass
-```
-
-The Python client doesn't store or expose quantile information at this time.
-
-### Histogram
-
-Histograms track the size and number of events in buckets.
-This allows for aggregatable calculation of quantiles.
-
-```python
-from prometheus_client import Histogram
-h = Histogram('request_latency_seconds', 'Description of histogram')
-h.observe(4.7) # Observe 4.7 (seconds in this case)
-```
-
-The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
-They can be overridden by passing `buckets` keyword argument to `Histogram`.
-
-There are utilities for timing code:
-
-```python
-@h.time()
-def f():
- pass
-
-with h.time():
- pass
-```
-
-### Info
-
-Info tracks key-value information, usually about a whole target.
-
-```python
-from prometheus_client import Info
-i = Info('my_build_version', 'Description of info')
-i.info({'version': '1.2.3', 'buildhost': 'foo@bar'})
-```
-
-### Enum
-
-Enum tracks which of a set of states something is currently in.
-
-```python
-from prometheus_client import Enum
-e = Enum('my_task_state', 'Description of enum',
- states=['starting', 'running', 'stopped'])
-e.state('running')
-```
-
-### Labels
-
-All metrics can have labels, allowing grouping of related time series.
-
-See the best practices on [naming](http://prometheus.io/docs/practices/naming/)
-and [labels](http://prometheus.io/docs/practices/instrumentation/#use-labels).
-
-Taking a counter as an example:
-
-```python
-from prometheus_client import Counter
-c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
-c.labels('get', '/').inc()
-c.labels('post', '/submit').inc()
-```
-
-Labels can also be passed as keyword-arguments:
-
-```python
-from prometheus_client import Counter
-c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
-c.labels(method='get', endpoint='/').inc()
-c.labels(method='post', endpoint='/submit').inc()
-```
-
-Metrics with labels are not initialized when declared, because the client can't
-know what values the label can have. It is recommended to initialize the label
-values by calling the `.labels()` method alone:
-
-```python
-from prometheus_client import Counter
-c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
-c.labels('get', '/')
-c.labels('post', '/submit')
-```
-
-### Exemplars
-
-Exemplars can be added to counter and histogram metrics. Exemplars can be
-specified by passing a dict of label value pairs to be exposed as the exemplar.
-For example with a counter:
-
-```python
-from prometheus_client import Counter
-c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
-c.labels('get', '/').inc(exemplar={'trace_id': 'abc123'})
-c.labels('post', '/submit').inc(1.0, {'trace_id': 'def456'})
-```
-
-And with a histogram:
-
-```python
-from prometheus_client import Histogram
-h = Histogram('request_latency_seconds', 'Description of histogram')
-h.observe(4.7, {'trace_id': 'abc123'})
-```
-
-Exemplars are only rendered in the OpenMetrics exposition format. If using the
-HTTP server or apps in this library, content negotiation can be used to specify
-OpenMetrics (which is done by default in Prometheus). Otherwise it will be
-necessary to use `generate_latest` from
-`prometheus_client.openmetrics.exposition` to view exemplars.
-
-To view exemplars in Prometheus it is also necessary to enable the the
-exemplar-storage feature flag:
-```
---enable-feature=exemplar-storage
-```
-Additional information is available in [the Prometheus
-documentation](https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage).
-
-### Disabling `_created` metrics
-
-By default counters, histograms, and summaries export an additional series
-suffixed with `_created` and a value of the unix timestamp for when the metric
-was created. If this information is not helpful, it can be disabled by setting
-the environment variable `PROMETHEUS_DISABLE_CREATED_SERIES=True`.
-
-### Process Collector
-
-The Python client automatically exports metrics about process CPU usage, RAM,
-file descriptors and start time. These all have the prefix `process`, and
-are only currently available on Linux.
-
-The namespace and pid constructor arguments allows for exporting metrics about
-other processes, for example:
-```
-ProcessCollector(namespace='mydaemon', pid=lambda: open('/var/run/daemon.pid').read())
-```
-
-### Platform Collector
-
-The client also automatically exports some metadata about Python. If using Jython,
-metadata about the JVM in use is also included. This information is available as
-labels on the `python_info` metric. The value of the metric is 1, since it is the
-labels that carry information.
-
-### Disabling Default Collector metrics
-
-By default the collected `process`, `gc`, and `platform` collector metrics are exported.
-If this information is not helpful, it can be disabled using the following:
-```python
-import prometheus_client
-
-prometheus_client.REGISTRY.unregister(prometheus_client.GC_COLLECTOR)
-prometheus_client.REGISTRY.unregister(prometheus_client.PLATFORM_COLLECTOR)
-prometheus_client.REGISTRY.unregister(prometheus_client.PROCESS_COLLECTOR)
-```
-
-## Exporting
-
-There are several options for exporting metrics.
-
-### HTTP
-
-Metrics are usually exposed over HTTP, to be read by the Prometheus server.
-
-The easiest way to do this is via `start_http_server`, which will start a HTTP
-server in a daemon thread on the given port:
-
-```python
-from prometheus_client import start_http_server
-
-start_http_server(8000)
-```
-
-Visit [http://localhost:8000/](http://localhost:8000/) to view the metrics.
-
-To add Prometheus exposition to an existing HTTP server, see the `MetricsHandler` class
-which provides a `BaseHTTPRequestHandler`. It also serves as a simple example of how
-to write a custom endpoint.
-
-By default, the prometheus client will accept only HTTP requests from Prometheus.
-To enable HTTPS, `certfile` and `keyfile` need to be provided. The certificate is
-presented to Prometheus as a server certificate during the TLS handshake, and
-the private key in the key file must belong to the public key in the certificate.
-
-When HTTPS is enabled, you can enable mutual TLS (mTLS) by setting `client_auth_required=True`
-(i.e. Prometheus is required to present a client certificate during TLS handshake) and the
-client certificate including its hostname is validated against the CA certificate chain.
-
-`client_cafile` can be used to specify a certificate file containing a CA certificate
-chain that is used to validate the client certificate. `client_capath` can be used to
-specify a certificate directory containing a CA certificate chain that is used to
-validate the client certificate. If neither of them is provided, a default CA certificate
-chain is used (see Python [ssl.SSLContext.load_default_certs()](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_default_certs))
-
-```python
-from prometheus_client import start_http_server
-
-start_http_server(8000, certfile="server.crt", keyfile="server.key")
-```
-
-#### Twisted
-
-To use prometheus with [twisted](https://twistedmatrix.com/), there is `MetricsResource` which exposes metrics as a twisted resource.
-
-```python
-from prometheus_client.twisted import MetricsResource
-from twisted.web.server import Site
-from twisted.web.resource import Resource
-from twisted.internet import reactor
-
-root = Resource()
-root.putChild(b'metrics', MetricsResource())
-
-factory = Site(root)
-reactor.listenTCP(8000, factory)
-reactor.run()
-```
-
-#### WSGI
-
-To use Prometheus with [WSGI](http://wsgi.readthedocs.org/en/latest/), there is
-`make_wsgi_app` which creates a WSGI application.
-
-```python
-from prometheus_client import make_wsgi_app
-from wsgiref.simple_server import make_server
-
-app = make_wsgi_app()
-httpd = make_server('', 8000, app)
-httpd.serve_forever()
-```
-
-Such an application can be useful when integrating Prometheus metrics with WSGI
-apps.
-
-The method `start_wsgi_server` can be used to serve the metrics through the
-WSGI reference implementation in a new thread.
-
-```python
-from prometheus_client import start_wsgi_server
+This package can be found on [PyPI](https://pypi.python.org/pypi/prometheus_client).
-start_wsgi_server(8000)
-```
-
-By default, the WSGI application will respect `Accept-Encoding:gzip` headers used by Prometheus
-and compress the response if such a header is present. This behaviour can be disabled by passing
-`disable_compression=True` when creating the app, like this:
-
-```python
-app = make_wsgi_app(disable_compression=True)
-```
-
-#### ASGI
-
-To use Prometheus with [ASGI](http://asgi.readthedocs.org/en/latest/), there is
-`make_asgi_app` which creates an ASGI application.
-
-```python
-from prometheus_client import make_asgi_app
-
-app = make_asgi_app()
-```
-Such an application can be useful when integrating Prometheus metrics with ASGI
-apps.
-
-By default, the WSGI application will respect `Accept-Encoding:gzip` headers used by Prometheus
-and compress the response if such a header is present. This behaviour can be disabled by passing
-`disable_compression=True` when creating the app, like this:
-
-```python
-app = make_asgi_app(disable_compression=True)
-```
-
-#### Flask
-
-To use Prometheus with [Flask](http://flask.pocoo.org/) we need to serve metrics through a Prometheus WSGI application. This can be achieved using [Flask's application dispatching](http://flask.pocoo.org/docs/latest/patterns/appdispatch/). Below is a working example.
-
-Save the snippet below in a `myapp.py` file
-
-```python
-from flask import Flask
-from werkzeug.middleware.dispatcher import DispatcherMiddleware
-from prometheus_client import make_wsgi_app
-
-# Create my app
-app = Flask(__name__)
-
-# Add prometheus wsgi middleware to route /metrics requests
-app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {
- '/metrics': make_wsgi_app()
-})
-```
-
-Run the example web application like this
-
-```bash
-# Install uwsgi if you do not have it
-pip install uwsgi
-uwsgi --http 127.0.0.1:8000 --wsgi-file myapp.py --callable app
-```
-
-Visit http://localhost:8000/metrics to see the metrics
-
-#### FastAPI + Gunicorn
-
-To use Prometheus with [FastAPI](https://fastapi.tiangolo.com/) and [Gunicorn](https://gunicorn.org/) we need to serve metrics through a Prometheus ASGI application.
-
-Save the snippet below in a `myapp.py` file
-
-```python
-from fastapi import FastAPI
-from prometheus_client import make_asgi_app
-
-# Create app
-app = FastAPI(debug=False)
-
-# Add prometheus asgi middleware to route /metrics requests
-metrics_app = make_asgi_app()
-app.mount("/metrics", metrics_app)
-```
-
-For Multiprocessing support, use this modified code snippet. Full multiprocessing instructions are provided [here](https://github.com/prometheus/client_python#multiprocess-mode-eg-gunicorn).
-
-```python
-from fastapi import FastAPI
-from prometheus_client import make_asgi_app
-
-app = FastAPI(debug=False)
-
-# Using multiprocess collector for registry
-def make_metrics_app():
- registry = CollectorRegistry()
- multiprocess.MultiProcessCollector(registry)
- return make_asgi_app(registry=registry)
-
-
-metrics_app = make_metrics_app()
-app.mount("/metrics", metrics_app)
-```
-
-Run the example web application like this
-
-```bash
-# Install gunicorn if you do not have it
-pip install gunicorn
-# If using multiple workers, add `--workers n` parameter to the line below
-gunicorn -b 127.0.0.1:8000 myapp:app -k uvicorn.workers.UvicornWorker
-```
-
-Visit http://localhost:8000/metrics to see the metrics
-
-
-### Node exporter textfile collector
-
-The [textfile collector](https://github.com/prometheus/node_exporter#textfile-collector)
-allows machine-level statistics to be exported out via the Node exporter.
-
-This is useful for monitoring cronjobs, or for writing cronjobs to expose metrics
-about a machine system that the Node exporter does not support or would not make sense
-to perform at every scrape (for example, anything involving subprocesses).
-
-```python
-from prometheus_client import CollectorRegistry, Gauge, write_to_textfile
-
-registry = CollectorRegistry()
-g = Gauge('raid_status', '1 if raid array is okay', registry=registry)
-g.set(1)
-write_to_textfile('/configured/textfile/path/raid.prom', registry)
-```
-
-A separate registry is used, as the default registry may contain other metrics
-such as those from the Process Collector.
-
-## Exporting to a Pushgateway
-
-The [Pushgateway](https://github.com/prometheus/pushgateway)
-allows ephemeral and batch jobs to expose their metrics to Prometheus.
-
-```python
-from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
-
-registry = CollectorRegistry()
-g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
-g.set_to_current_time()
-push_to_gateway('localhost:9091', job='batchA', registry=registry)
-```
-
-A separate registry is used, as the default registry may contain other metrics
-such as those from the Process Collector.
-
-Pushgateway functions take a grouping key. `push_to_gateway` replaces metrics
-with the same grouping key, `pushadd_to_gateway` only replaces metrics with the
-same name and grouping key and `delete_from_gateway` deletes metrics with the
-given job and grouping key. See the
-[Pushgateway documentation](https://github.com/prometheus/pushgateway/blob/master/README.md)
-for more information.
-
-`instance_ip_grouping_key` returns a grouping key with the instance label set
-to the host's IP address.
-
-### Handlers for authentication
-
-If the push gateway you are connecting to is protected with HTTP Basic Auth,
-you can use a special handler to set the Authorization header.
-
-```python
-from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
-from prometheus_client.exposition import basic_auth_handler
-
-def my_auth_handler(url, method, timeout, headers, data):
- username = 'foobar'
- password = 'secret123'
- return basic_auth_handler(url, method, timeout, headers, data, username, password)
-registry = CollectorRegistry()
-g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
-g.set_to_current_time()
-push_to_gateway('localhost:9091', job='batchA', registry=registry, handler=my_auth_handler)
-```
-
-TLS Auth is also supported when using the push gateway with a special handler.
-
-```python
-from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
-from prometheus_client.exposition import tls_auth_handler
-
-
-def my_auth_handler(url, method, timeout, headers, data):
- certfile = 'client-crt.pem'
- keyfile = 'client-key.pem'
- return tls_auth_handler(url, method, timeout, headers, data, certfile, keyfile)
-
-registry = CollectorRegistry()
-g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
-g.set_to_current_time()
-push_to_gateway('localhost:9091', job='batchA', registry=registry, handler=my_auth_handler)
-```
-
-## Bridges
-
-It is also possible to expose metrics to systems other than Prometheus.
-This allows you to take advantage of Prometheus instrumentation even
-if you are not quite ready to fully transition to Prometheus yet.
-
-### Graphite
-
-Metrics are pushed over TCP in the Graphite plaintext format.
-
-```python
-from prometheus_client.bridge.graphite import GraphiteBridge
-
-gb = GraphiteBridge(('graphite.your.org', 2003))
-# Push once.
-gb.push()
-# Push every 10 seconds in a daemon thread.
-gb.start(10.0)
-```
-
-Graphite [tags](https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/) are also supported.
-
-```python
-from prometheus_client.bridge.graphite import GraphiteBridge
-
-gb = GraphiteBridge(('graphite.your.org', 2003), tags=True)
-c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
-c.labels('get', '/').inc()
-gb.push()
-```
-
-## Custom Collectors
-
-Sometimes it is not possible to directly instrument code, as it is not
-in your control. This requires you to proxy metrics from other systems.
-
-To do so you need to create a custom collector, for example:
-
-```python
-from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY
-from prometheus_client.registry import Collector
-
-class CustomCollector(Collector):
- def collect(self):
- yield GaugeMetricFamily('my_gauge', 'Help text', value=7)
- c = CounterMetricFamily('my_counter_total', 'Help text', labels=['foo'])
- c.add_metric(['bar'], 1.7)
- c.add_metric(['baz'], 3.8)
- yield c
-
-REGISTRY.register(CustomCollector())
-```
-
-`SummaryMetricFamily`, `HistogramMetricFamily` and `InfoMetricFamily` work similarly.
-
-A collector may implement a `describe` method which returns metrics in the same
-format as `collect` (though you don't have to include the samples). This is
-used to predetermine the names of time series a `CollectorRegistry` exposes and
-thus to detect collisions and duplicate registrations.
-
-Usually custom collectors do not have to implement `describe`. If `describe` is
-not implemented and the CollectorRegistry was created with `auto_describe=True`
-(which is the case for the default registry) then `collect` will be called at
-registration time instead of `describe`. If this could cause problems, either
-implement a proper `describe`, or if that's not practical have `describe`
-return an empty list.
-
-
-## Multiprocess Mode (E.g. Gunicorn)
-
-Prometheus client libraries presume a threaded model, where metrics are shared
-across workers. This doesn't work so well for languages such as Python where
-it's common to have processes rather than threads to handle large workloads.
-
-To handle this the client library can be put in multiprocess mode.
-This comes with a number of limitations:
-
-- Registries can not be used as normal, all instantiated metrics are exported
- - Registering metrics to a registry later used by a `MultiProcessCollector`
- may cause duplicate metrics to be exported
-- Custom collectors do not work (e.g. cpu and memory metrics)
-- Info and Enum metrics do not work
-- The pushgateway cannot be used
-- Gauges cannot use the `pid` label
-- Exemplars are not supported
-
-There's several steps to getting this working:
-
-**1. Deployment**:
-
-The `PROMETHEUS_MULTIPROC_DIR` environment variable must be set to a directory
-that the client library can use for metrics. This directory must be wiped
-between process/Gunicorn runs (before startup is recommended).
-
-This environment variable should be set from a start-up shell script,
-and not directly from Python (otherwise it may not propagate to child processes).
-
-**2. Metrics collector**:
-
-The application must initialize a new `CollectorRegistry`, and store the
-multi-process collector inside. It is a best practice to create this registry
-inside the context of a request to avoid metrics registering themselves to a
-collector used by a `MultiProcessCollector`. If a registry with metrics
-registered is used by a `MultiProcessCollector` duplicate metrics may be
-exported, one for multiprocess, and one for the process serving the request.
-
-```python
-from prometheus_client import multiprocess
-from prometheus_client import generate_latest, CollectorRegistry, CONTENT_TYPE_LATEST, Counter
-
-MY_COUNTER = Counter('my_counter', 'Description of my counter')
-
-# Expose metrics.
-def app(environ, start_response):
- registry = CollectorRegistry()
- multiprocess.MultiProcessCollector(registry)
- data = generate_latest(registry)
- status = '200 OK'
- response_headers = [
- ('Content-type', CONTENT_TYPE_LATEST),
- ('Content-Length', str(len(data)))
- ]
- start_response(status, response_headers)
- return iter([data])
-```
-
-**3. Gunicorn configuration**:
-
-The `gunicorn` configuration file needs to include the following function:
-
-```python
-from prometheus_client import multiprocess
-
-def child_exit(server, worker):
- multiprocess.mark_process_dead(worker.pid)
-```
-
-**4. Metrics tuning (Gauge)**:
-
-When `Gauge`s are used in multiprocess applications,
-you must decide how to handle the metrics reported by each process.
-Gauges have several modes they can run in, which can be selected with the `multiprocess_mode` parameter.
-
-- 'all': Default. Return a timeseries per process (alive or dead), labelled by the process's `pid` (the label is added internally).
-- 'min': Return a single timeseries that is the minimum of the values of all processes (alive or dead).
-- 'max': Return a single timeseries that is the maximum of the values of all processes (alive or dead).
-- 'sum': Return a single timeseries that is the sum of the values of all processes (alive or dead).
-- 'mostrecent': Return a single timeseries that is the most recent value among all processes (alive or dead).
-
-Prepend 'live' to the beginning of the mode to return the same result but only considering living processes
-(e.g., 'liveall, 'livesum', 'livemax', 'livemin', 'livemostrecent').
-
-```python
-from prometheus_client import Gauge
-
-# Example gauge
-IN_PROGRESS = Gauge("inprogress_requests", "help", multiprocess_mode='livesum')
-```
-
-
-## Parser
-
-The Python client supports parsing the Prometheus text format.
-This is intended for advanced use cases where you have servers
-exposing Prometheus metrics and need to get them into some other
-system.
-
-```python
-from prometheus_client.parser import text_string_to_metric_families
-for family in text_string_to_metric_families(u"my_gauge 1.0\n"):
- for sample in family.samples:
- print("Name: {0} Labels: {1} Value: {2}".format(*sample))
-```
-
-## Restricted registry
-
-Registries support restriction to only return specific metrics.
-If you’re using the built-in HTTP server, you can use the GET parameter "name[]", since it’s an array it can be used multiple times.
-If you’re directly using `generate_latest`, you can use the function `restricted_registry()`.
-
-```python
-curl --get --data-urlencode "name[]=python_gc_objects_collected_total" --data-urlencode "name[]=python_info" http://127.0.0.1:9200/metrics
-```
-
-```python
-from prometheus_client import generate_latest
-
-generate_latest(REGISTRY.restricted_registry(['python_gc_objects_collected_total', 'python_info']))
-```
-
-```python
-# HELP python_info Python platform information
-# TYPE python_info gauge
-python_info{implementation="CPython",major="3",minor="9",patchlevel="3",version="3.9.3"} 1.0
-# HELP python_gc_objects_collected_total Objects collected during gc
-# TYPE python_gc_objects_collected_total counter
-python_gc_objects_collected_total{generation="0"} 73129.0
-python_gc_objects_collected_total{generation="1"} 8594.0
-python_gc_objects_collected_total{generation="2"} 296.0
-```
+## Documentation
+Documentation is available on https://prometheus.github.io/client_python
## Links
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..5c41f011
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1 @@
+.hugo_build.lock
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..a9e5c37a
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,32 @@
+# Docs
+
+This directory contains [hugo](https://gohugo.io) documentation to be published in Github pages.
+
+## Dependencies
+
+- [Geekdocs v1.5.0](https://github.com/thegeeklab/hugo-geekdoc/releases/tag/v1.5.0)
+- [Hugo v0.145.0](https://github.com/gohugoio/hugo/releases/tag/v0.145.0)
+
+## Run Locally
+
+To serve the documentation locally, run the following command:
+
+```shell
+hugo server -D
+```
+
+This will serve the docs on [http://localhost:1313](http://localhost:1313).
+
+## Update Geekdocs
+
+The docs use the [Geekdocs](https://geekdocs.de/) theme. The theme is checked in to Github in the `./docs/themes/hugo-geekdoc/` folder. To update [Geekdocs](https://geekdocs.de/), remove the current folder and create a new one with the latest [release](https://github.com/thegeeklab/hugo-geekdoc/releases). There are no local modifications in `./docs/themes/hugo-geekdoc/`.
+
+```shell
+rm -rf ./docs/themes/hugo-geekdoc
+mkdir -p themes/hugo-geekdoc/
+curl -L https://github.com/thegeeklab/hugo-geekdoc/releases/latest/download/hugo-geekdoc.tar.gz | tar -xz -C themes/hugo-geekdoc/ --strip-components=1
+```
+
+## Deploy to Github Pages
+
+Changes to the `master` branch will be deployed automatically with Github actions.
diff --git a/docs/archetypes/default.md b/docs/archetypes/default.md
new file mode 100644
index 00000000..c6f3fcef
--- /dev/null
+++ b/docs/archetypes/default.md
@@ -0,0 +1,5 @@
++++
+title = '{{ replace .File.ContentBaseName "-" " " | title }}'
+date = {{ .Date }}
+draft = true
++++
diff --git a/docs/content/_index.md b/docs/content/_index.md
new file mode 100644
index 00000000..1be1b90c
--- /dev/null
+++ b/docs/content/_index.md
@@ -0,0 +1,49 @@
+---
+title: client_python
+weight: 1
+---
+
+This tutorial shows the quickest way to get started with the Prometheus Python library.
+
+**One**: Install the client:
+
+```shell
+pip install prometheus-client
+```
+
+**Two**: Paste the following into a Python interpreter:
+
+```python
+from prometheus_client import start_http_server, Summary
+import random
+import time
+
+# Create a metric to track time spent and requests made.
+REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')
+
+# Decorate function with metric.
+@REQUEST_TIME.time()
+def process_request(t):
+ """A dummy function that takes some time."""
+ time.sleep(t)
+
+if __name__ == '__main__':
+ # Start up the server to expose the metrics.
+ start_http_server(8000)
+ # Generate some requests.
+ while True:
+ process_request(random.random())
+```
+
+**Three**: Visit [http://localhost:8000/](http://localhost:8000/) to view the metrics.
+
+From one easy to use decorator you get:
+
+* `request_processing_seconds_count`: Number of times this function was called.
+* `request_processing_seconds_sum`: Total amount of time spent in this function.
+
+Prometheus's `rate` function allows calculation of both requests per second,
+and latency over time from this data.
+
+In addition if you're on Linux the `process` metrics expose CPU, memory and
+other information about the process for free!
diff --git a/docs/content/bridges/_index.md b/docs/content/bridges/_index.md
new file mode 100644
index 00000000..8ebdb0c3
--- /dev/null
+++ b/docs/content/bridges/_index.md
@@ -0,0 +1,8 @@
+---
+title: Bridges
+weight: 5
+---
+
+It is also possible to expose metrics to systems other than Prometheus.
+This allows you to take advantage of Prometheus instrumentation even
+if you are not quite ready to fully transition to Prometheus yet.
\ No newline at end of file
diff --git a/docs/content/bridges/graphite.md b/docs/content/bridges/graphite.md
new file mode 100644
index 00000000..fe29905d
--- /dev/null
+++ b/docs/content/bridges/graphite.md
@@ -0,0 +1,27 @@
+---
+title: Graphite
+weight: 1
+---
+
+Metrics are pushed over TCP in the Graphite plaintext format.
+
+```python
+from prometheus_client.bridge.graphite import GraphiteBridge
+
+gb = GraphiteBridge(('graphite.your.org', 2003))
+# Push once.
+gb.push()
+# Push every 10 seconds in a daemon thread.
+gb.start(10.0)
+```
+
+Graphite [tags](https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/) are also supported.
+
+```python
+from prometheus_client.bridge.graphite import GraphiteBridge
+
+gb = GraphiteBridge(('graphite.your.org', 2003), tags=True)
+c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
+c.labels('get', '/').inc()
+gb.push()
+```
\ No newline at end of file
diff --git a/docs/content/collector/_index.md b/docs/content/collector/_index.md
new file mode 100644
index 00000000..957c8ba9
--- /dev/null
+++ b/docs/content/collector/_index.md
@@ -0,0 +1,36 @@
+---
+title: Collector
+weight: 3
+---
+
+# Process Collector
+
+The Python client automatically exports metrics about process CPU usage, RAM,
+file descriptors and start time. These all have the prefix `process`, and
+are only currently available on Linux.
+
+The namespace and pid constructor arguments allows for exporting metrics about
+other processes, for example:
+```
+ProcessCollector(namespace='mydaemon', pid=lambda: open('/var/run/daemon.pid').read())
+```
+
+# Platform Collector
+
+The client also automatically exports some metadata about Python. If using Jython,
+metadata about the JVM in use is also included. This information is available as
+labels on the `python_info` metric. The value of the metric is 1, since it is the
+labels that carry information.
+
+# Disabling Default Collector metrics
+
+By default the collected `process`, `gc`, and `platform` collector metrics are exported.
+If this information is not helpful, it can be disabled using the following:
+
+```python
+import prometheus_client
+
+prometheus_client.REGISTRY.unregister(prometheus_client.GC_COLLECTOR)
+prometheus_client.REGISTRY.unregister(prometheus_client.PLATFORM_COLLECTOR)
+prometheus_client.REGISTRY.unregister(prometheus_client.PROCESS_COLLECTOR)
+```
\ No newline at end of file
diff --git a/docs/content/collector/custom.md b/docs/content/collector/custom.md
new file mode 100644
index 00000000..bc6a021c
--- /dev/null
+++ b/docs/content/collector/custom.md
@@ -0,0 +1,38 @@
+---
+title: Custom Collectors
+weight: 1
+---
+
+Sometimes it is not possible to directly instrument code, as it is not
+in your control. This requires you to proxy metrics from other systems.
+
+To do so you need to create a custom collector, for example:
+
+```python
+from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY
+from prometheus_client.registry import Collector
+
+class CustomCollector(Collector):
+ def collect(self):
+ yield GaugeMetricFamily('my_gauge', 'Help text', value=7)
+ c = CounterMetricFamily('my_counter_total', 'Help text', labels=['foo'])
+ c.add_metric(['bar'], 1.7)
+ c.add_metric(['baz'], 3.8)
+ yield c
+
+REGISTRY.register(CustomCollector())
+```
+
+`SummaryMetricFamily`, `HistogramMetricFamily` and `InfoMetricFamily` work similarly.
+
+A collector may implement a `describe` method which returns metrics in the same
+format as `collect` (though you don't have to include the samples). This is
+used to predetermine the names of time series a `CollectorRegistry` exposes and
+thus to detect collisions and duplicate registrations.
+
+Usually custom collectors do not have to implement `describe`. If `describe` is
+not implemented and the CollectorRegistry was created with `auto_describe=True`
+(which is the case for the default registry) then `collect` will be called at
+registration time instead of `describe`. If this could cause problems, either
+implement a proper `describe`, or if that's not practical have `describe`
+return an empty list.
\ No newline at end of file
diff --git a/docs/content/exporting/_index.md b/docs/content/exporting/_index.md
new file mode 100644
index 00000000..1e532f85
--- /dev/null
+++ b/docs/content/exporting/_index.md
@@ -0,0 +1,6 @@
+---
+title: Exporting
+weight: 4
+---
+
+There are several options for exporting metrics.
diff --git a/docs/content/exporting/http/_index.md b/docs/content/exporting/http/_index.md
new file mode 100644
index 00000000..dc1b8f2c
--- /dev/null
+++ b/docs/content/exporting/http/_index.md
@@ -0,0 +1,74 @@
+---
+title: HTTP/HTTPS
+weight: 1
+---
+
+# HTTP
+
+Metrics are usually exposed over HTTP, to be read by the Prometheus server.
+
+The easiest way to do this is via `start_http_server`, which will start a HTTP
+server in a daemon thread on the given port:
+
+```python
+from prometheus_client import start_http_server
+
+start_http_server(8000)
+```
+
+Visit [http://localhost:8000/](http://localhost:8000/) to view the metrics.
+
+The function will return the HTTP server and thread objects, which can be used
+to shutdown the server gracefully:
+
+```python
+server, t = start_http_server(8000)
+server.shutdown()
+t.join()
+```
+
+To add Prometheus exposition to an existing HTTP server, see the `MetricsHandler` class
+which provides a `BaseHTTPRequestHandler`. It also serves as a simple example of how
+to write a custom endpoint.
+
+# HTTPS
+
+By default, the prometheus client will accept only HTTP requests from Prometheus.
+To enable HTTPS, `certfile` and `keyfile` need to be provided. The certificate is
+presented to Prometheus as a server certificate during the TLS handshake, and
+the private key in the key file must belong to the public key in the certificate.
+
+When HTTPS is enabled, you can enable mutual TLS (mTLS) by setting `client_auth_required=True`
+(i.e. Prometheus is required to present a client certificate during TLS handshake) and the
+client certificate including its hostname is validated against the CA certificate chain.
+
+`client_cafile` can be used to specify a certificate file containing a CA certificate
+chain that is used to validate the client certificate. `client_capath` can be used to
+specify a certificate directory containing a CA certificate chain that is used to
+validate the client certificate. If neither of them is provided, a default CA certificate
+chain is used (see Python [ssl.SSLContext.load_default_certs()](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_default_certs))
+
+```python
+from prometheus_client import start_http_server
+
+start_http_server(8000, certfile="server.crt", keyfile="server.key")
+```
+
+# Supported HTTP methods
+
+The prometheus client will handle the following HTTP methods and resources:
+
+* `OPTIONS (any)` - returns HTTP status 200 and an 'Allow' header indicating the
+ allowed methods (OPTIONS, GET)
+* `GET (any)` - returns HTTP status 200 and the metrics data
+* `GET /favicon.ico` - returns HTTP status 200 and an empty response body. Some
+ browsers support this to display the returned icon in the browser tab.
+
+Other HTTP methods than these are rejected with HTTP status 405 "Method Not Allowed"
+and an 'Allow' header indicating the allowed methods (OPTIONS, GET).
+
+Any returned HTTP errors are also displayed in the response body after a hash
+sign and with a brief hint. Example:
+```
+# HTTP 405 Method Not Allowed: XXX; use OPTIONS or GET
+```
diff --git a/docs/content/exporting/http/asgi.md b/docs/content/exporting/http/asgi.md
new file mode 100644
index 00000000..5b9d5430
--- /dev/null
+++ b/docs/content/exporting/http/asgi.md
@@ -0,0 +1,23 @@
+---
+title: ASGI
+weight: 3
+---
+
+To use Prometheus with [ASGI](http://asgi.readthedocs.org/en/latest/), there is
+`make_asgi_app` which creates an ASGI application.
+
+```python
+from prometheus_client import make_asgi_app
+
+app = make_asgi_app()
+```
+Such an application can be useful when integrating Prometheus metrics with ASGI
+apps.
+
+By default, the ASGI application will respect `Accept-Encoding:gzip` headers used by Prometheus
+and compress the response if such a header is present. This behaviour can be disabled by passing
+`disable_compression=True` when creating the app, like this:
+
+```python
+app = make_asgi_app(disable_compression=True)
+```
diff --git a/docs/content/exporting/http/fastapi-gunicorn.md b/docs/content/exporting/http/fastapi-gunicorn.md
new file mode 100644
index 00000000..148a36d7
--- /dev/null
+++ b/docs/content/exporting/http/fastapi-gunicorn.md
@@ -0,0 +1,50 @@
+---
+title: FastAPI + Gunicorn
+weight: 5
+---
+
+To use Prometheus with [FastAPI](https://fastapi.tiangolo.com/) and [Gunicorn](https://gunicorn.org/) we need to serve metrics through a Prometheus ASGI application.
+
+Save the snippet below in a `myapp.py` file
+
+```python
+from fastapi import FastAPI
+from prometheus_client import make_asgi_app
+
+# Create app
+app = FastAPI(debug=False)
+
+# Add prometheus asgi middleware to route /metrics requests
+metrics_app = make_asgi_app()
+app.mount("/metrics", metrics_app)
+```
+
+For Multiprocessing support, use this modified code snippet. Full multiprocessing instructions are provided [here]({{< ref "/multiprocess" >}}).
+
+```python
+from fastapi import FastAPI
+from prometheus_client import make_asgi_app
+
+app = FastAPI(debug=False)
+
+# Using multiprocess collector for registry
+def make_metrics_app():
+ registry = CollectorRegistry()
+ multiprocess.MultiProcessCollector(registry)
+ return make_asgi_app(registry=registry)
+
+
+metrics_app = make_metrics_app()
+app.mount("/metrics", metrics_app)
+```
+
+Run the example web application like this
+
+```bash
+# Install gunicorn if you do not have it
+pip install gunicorn
+# If using multiple workers, add `--workers n` parameter to the line below
+gunicorn -b 127.0.0.1:8000 myapp:app -k uvicorn.workers.UvicornWorker
+```
+
+Visit http://localhost:8000/metrics to see the metrics
diff --git a/docs/content/exporting/http/flask.md b/docs/content/exporting/http/flask.md
new file mode 100644
index 00000000..a666a182
--- /dev/null
+++ b/docs/content/exporting/http/flask.md
@@ -0,0 +1,32 @@
+---
+title: Flask
+weight: 4
+---
+
+To use Prometheus with [Flask](http://flask.pocoo.org/) we need to serve metrics through a Prometheus WSGI application. This can be achieved using [Flask's application dispatching](http://flask.pocoo.org/docs/latest/patterns/appdispatch/). Below is a working example.
+
+Save the snippet below in a `myapp.py` file
+
+```python
+from flask import Flask
+from werkzeug.middleware.dispatcher import DispatcherMiddleware
+from prometheus_client import make_wsgi_app
+
+# Create my app
+app = Flask(__name__)
+
+# Add prometheus wsgi middleware to route /metrics requests
+app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {
+ '/metrics': make_wsgi_app()
+})
+```
+
+Run the example web application like this
+
+```bash
+# Install uwsgi if you do not have it
+pip install uwsgi
+uwsgi --http 127.0.0.1:8000 --wsgi-file myapp.py --callable app
+```
+
+Visit http://localhost:8000/metrics to see the metrics
\ No newline at end of file
diff --git a/docs/content/exporting/http/twisted.md b/docs/content/exporting/http/twisted.md
new file mode 100644
index 00000000..a45fe919
--- /dev/null
+++ b/docs/content/exporting/http/twisted.md
@@ -0,0 +1,20 @@
+---
+title: Twisted
+weight: 1
+---
+
+To use prometheus with [twisted](https://twistedmatrix.com/), there is `MetricsResource` which exposes metrics as a twisted resource.
+
+```python
+from prometheus_client.twisted import MetricsResource
+from twisted.web.server import Site
+from twisted.web.resource import Resource
+from twisted.internet import reactor
+
+root = Resource()
+root.putChild(b'metrics', MetricsResource())
+
+factory = Site(root)
+reactor.listenTCP(8000, factory)
+reactor.run()
+```
\ No newline at end of file
diff --git a/docs/content/exporting/http/wsgi.md b/docs/content/exporting/http/wsgi.md
new file mode 100644
index 00000000..3c3bc3e7
--- /dev/null
+++ b/docs/content/exporting/http/wsgi.md
@@ -0,0 +1,36 @@
+---
+title: WSGI
+weight: 2
+---
+
+To use Prometheus with [WSGI](http://wsgi.readthedocs.org/en/latest/), there is
+`make_wsgi_app` which creates a WSGI application.
+
+```python
+from prometheus_client import make_wsgi_app
+from wsgiref.simple_server import make_server
+
+app = make_wsgi_app()
+httpd = make_server('', 8000, app)
+httpd.serve_forever()
+```
+
+Such an application can be useful when integrating Prometheus metrics with WSGI
+apps.
+
+The method `start_wsgi_server` can be used to serve the metrics through the
+WSGI reference implementation in a new thread.
+
+```python
+from prometheus_client import start_wsgi_server
+
+start_wsgi_server(8000)
+```
+
+By default, the WSGI application will respect `Accept-Encoding:gzip` headers used by Prometheus
+and compress the response if such a header is present. This behaviour can be disabled by passing
+`disable_compression=True` when creating the app, like this:
+
+```python
+app = make_wsgi_app(disable_compression=True)
+```
\ No newline at end of file
diff --git a/docs/content/exporting/pushgateway.md b/docs/content/exporting/pushgateway.md
new file mode 100644
index 00000000..bf5eb112
--- /dev/null
+++ b/docs/content/exporting/pushgateway.md
@@ -0,0 +1,73 @@
+---
+title: Pushgateway
+weight: 3
+---
+
+The [Pushgateway](https://github.com/prometheus/pushgateway)
+allows ephemeral and batch jobs to expose their metrics to Prometheus.
+Since Prometheus may not be able to scrape such a target, the targets can
+push their metrics to a separate instance of the Pushgateway,
+which then exposes these metrics to Prometheus.
+
+```python
+from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
+
+registry = CollectorRegistry()
+g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
+g.set_to_current_time()
+push_to_gateway('localhost:9091', job='batchA', registry=registry)
+```
+
+A separate registry is used, as the default registry may contain other metrics
+such as those from the Process Collector.
+
+Pushgateway functions take a grouping key.
+1. `push_to_gateway` replaces metrics
+with the same grouping key.
+2. `pushadd_to_gateway` only replaces metrics with the
+same name and grouping key.
+3. `delete_from_gateway` deletes metrics with the
+given job and grouping key.
+4. `instance_ip_grouping_key` returns a grouping key with the instance label set
+to the host's IP address.
+
+See the
+[Pushgateway documentation](https://github.com/prometheus/pushgateway/blob/master/README.md)
+for more information.
+
+# Handlers for authentication
+
+If the push gateway you are connecting to is protected with HTTP Basic Auth,
+you can use a special handler to set the Authorization header.
+
+```python
+from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
+from prometheus_client.exposition import basic_auth_handler
+
+def my_auth_handler(url, method, timeout, headers, data):
+ username = 'foobar'
+ password = 'secret123'
+ return basic_auth_handler(url, method, timeout, headers, data, username, password)
+registry = CollectorRegistry()
+g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
+g.set_to_current_time()
+push_to_gateway('localhost:9091', job='batchA', registry=registry, handler=my_auth_handler)
+```
+
+TLS Auth is also supported when using the push gateway with a special handler.
+
+```python
+from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
+from prometheus_client.exposition import tls_auth_handler
+
+
+def my_auth_handler(url, method, timeout, headers, data):
+ certfile = 'client-crt.pem'
+ keyfile = 'client-key.pem'
+ return tls_auth_handler(url, method, timeout, headers, data, certfile, keyfile)
+
+registry = CollectorRegistry()
+g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
+g.set_to_current_time()
+push_to_gateway('localhost:9091', job='batchA', registry=registry, handler=my_auth_handler)
+```
diff --git a/docs/content/exporting/textfile.md b/docs/content/exporting/textfile.md
new file mode 100644
index 00000000..80360e46
--- /dev/null
+++ b/docs/content/exporting/textfile.md
@@ -0,0 +1,23 @@
+---
+title: Node exporter textfile collector
+weight: 2
+---
+
+The [textfile collector](https://github.com/prometheus/node_exporter#textfile-collector)
+allows machine-level statistics to be exported out via the Node exporter.
+
+This is useful for monitoring cronjobs, or for writing cronjobs to expose metrics
+about a machine system that the Node exporter does not support or would not make sense
+to perform at every scrape (for example, anything involving subprocesses).
+
+```python
+from prometheus_client import CollectorRegistry, Gauge, write_to_textfile
+
+registry = CollectorRegistry()
+g = Gauge('raid_status', '1 if raid array is okay', registry=registry)
+g.set(1)
+write_to_textfile('/configured/textfile/path/raid.prom', registry)
+```
+
+A separate registry is used, as the default registry may contain other metrics
+such as those from the Process Collector.
\ No newline at end of file
diff --git a/docs/content/instrumenting/_index.md b/docs/content/instrumenting/_index.md
new file mode 100644
index 00000000..13bbc6b6
--- /dev/null
+++ b/docs/content/instrumenting/_index.md
@@ -0,0 +1,20 @@
+---
+title: Instrumenting
+weight: 2
+---
+
+Four types of metric are offered: Counter, Gauge, Summary and Histogram.
+See the documentation on [metric types](http://prometheus.io/docs/concepts/metric_types/)
+and [instrumentation best practices](https://prometheus.io/docs/practices/instrumentation/#counter-vs-gauge-summary-vs-histogram)
+on how to use them.
+
+## Disabling `_created` metrics
+
+By default counters, histograms, and summaries export an additional series
+suffixed with `_created` and a value of the unix timestamp for when the metric
+was created. If this information is not helpful, it can be disabled by setting
+the environment variable `PROMETHEUS_DISABLE_CREATED_SERIES=True` or in code:
+```python
+from prometheus_client import disable_created_metrics
+disable_created_metrics()
+```
diff --git a/docs/content/instrumenting/counter.md b/docs/content/instrumenting/counter.md
new file mode 100644
index 00000000..94618025
--- /dev/null
+++ b/docs/content/instrumenting/counter.md
@@ -0,0 +1,34 @@
+---
+title: Counter
+weight: 1
+---
+
+Counters go up, and reset when the process restarts.
+
+
+```python
+from prometheus_client import Counter
+c = Counter('my_failures', 'Description of counter')
+c.inc() # Increment by 1
+c.inc(1.6) # Increment by given value
+```
+
+If there is a suffix of `_total` on the metric name, it will be removed. When
+exposing the time series for counter, a `_total` suffix will be added. This is
+for compatibility between OpenMetrics and the Prometheus text format, as OpenMetrics
+requires the `_total` suffix.
+
+There are utilities to count exceptions raised:
+
+```python
+@c.count_exceptions()
+def f():
+ pass
+
+with c.count_exceptions():
+ pass
+
+# Count only one type of exception
+with c.count_exceptions(ValueError):
+ pass
+```
\ No newline at end of file
diff --git a/docs/content/instrumenting/enum.md b/docs/content/instrumenting/enum.md
new file mode 100644
index 00000000..102091a1
--- /dev/null
+++ b/docs/content/instrumenting/enum.md
@@ -0,0 +1,13 @@
+---
+title: Enum
+weight: 6
+---
+
+Enum tracks which of a set of states something is currently in.
+
+```python
+from prometheus_client import Enum
+e = Enum('my_task_state', 'Description of enum',
+ states=['starting', 'running', 'stopped'])
+e.state('running')
+```
\ No newline at end of file
diff --git a/docs/content/instrumenting/exemplars.md b/docs/content/instrumenting/exemplars.md
new file mode 100644
index 00000000..d472aadc
--- /dev/null
+++ b/docs/content/instrumenting/exemplars.md
@@ -0,0 +1,37 @@
+---
+title: Exemplars
+weight: 8
+---
+
+Exemplars can be added to counter and histogram metrics. Exemplars can be
+specified by passing a dict of label value pairs to be exposed as the exemplar.
+For example with a counter:
+
+```python
+from prometheus_client import Counter
+c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
+c.labels('get', '/').inc(exemplar={'trace_id': 'abc123'})
+c.labels('post', '/submit').inc(1.0, {'trace_id': 'def456'})
+```
+
+And with a histogram:
+
+```python
+from prometheus_client import Histogram
+h = Histogram('request_latency_seconds', 'Description of histogram')
+h.observe(4.7, {'trace_id': 'abc123'})
+```
+
+Exemplars are only rendered in the OpenMetrics exposition format. If using the
+HTTP server or apps in this library, content negotiation can be used to specify
+OpenMetrics (which is done by default in Prometheus). Otherwise it will be
+necessary to use `generate_latest` from
+`prometheus_client.openmetrics.exposition` to view exemplars.
+
+To view exemplars in Prometheus it is also necessary to enable the the
+exemplar-storage feature flag:
+```
+--enable-feature=exemplar-storage
+```
+Additional information is available in [the Prometheus
+documentation](https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage).
diff --git a/docs/content/instrumenting/gauge.md b/docs/content/instrumenting/gauge.md
new file mode 100644
index 00000000..0b1529e9
--- /dev/null
+++ b/docs/content/instrumenting/gauge.md
@@ -0,0 +1,36 @@
+---
+title: Gauge
+weight: 2
+---
+
+Gauges can go up and down.
+
+```python
+from prometheus_client import Gauge
+g = Gauge('my_inprogress_requests', 'Description of gauge')
+g.inc() # Increment by 1
+g.dec(10) # Decrement by given value
+g.set(4.2) # Set to a given value
+```
+
+There are utilities for common use cases:
+
+```python
+g.set_to_current_time() # Set to current unixtime
+
+# Increment when entered, decrement when exited.
+@g.track_inprogress()
+def f():
+ pass
+
+with g.track_inprogress():
+ pass
+```
+
+A Gauge can also take its value from a callback:
+
+```python
+d = Gauge('data_objects', 'Number of objects')
+my_dict = {}
+d.set_function(lambda: len(my_dict))
+```
\ No newline at end of file
diff --git a/docs/content/instrumenting/histogram.md b/docs/content/instrumenting/histogram.md
new file mode 100644
index 00000000..cb85f183
--- /dev/null
+++ b/docs/content/instrumenting/histogram.md
@@ -0,0 +1,27 @@
+---
+title: Histogram
+weight: 4
+---
+
+Histograms track the size and number of events in buckets.
+This allows for aggregatable calculation of quantiles.
+
+```python
+from prometheus_client import Histogram
+h = Histogram('request_latency_seconds', 'Description of histogram')
+h.observe(4.7) # Observe 4.7 (seconds in this case)
+```
+
+The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
+They can be overridden by passing `buckets` keyword argument to `Histogram`.
+
+There are utilities for timing code:
+
+```python
+@h.time()
+def f():
+ pass
+
+with h.time():
+ pass
+```
\ No newline at end of file
diff --git a/docs/content/instrumenting/info.md b/docs/content/instrumenting/info.md
new file mode 100644
index 00000000..6334d92b
--- /dev/null
+++ b/docs/content/instrumenting/info.md
@@ -0,0 +1,12 @@
+---
+title: Info
+weight: 5
+---
+
+Info tracks key-value information, usually about a whole target.
+
+```python
+from prometheus_client import Info
+i = Info('my_build_version', 'Description of info')
+i.info({'version': '1.2.3', 'buildhost': 'foo@bar'})
+```
diff --git a/docs/content/instrumenting/labels.md b/docs/content/instrumenting/labels.md
new file mode 100644
index 00000000..ebf80b56
--- /dev/null
+++ b/docs/content/instrumenting/labels.md
@@ -0,0 +1,38 @@
+---
+title: Labels
+weight: 7
+---
+
+All metrics can have labels, allowing grouping of related time series.
+
+See the best practices on [naming](http://prometheus.io/docs/practices/naming/)
+and [labels](http://prometheus.io/docs/practices/instrumentation/#use-labels).
+
+Taking a counter as an example:
+
+```python
+from prometheus_client import Counter
+c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
+c.labels('get', '/').inc()
+c.labels('post', '/submit').inc()
+```
+
+Labels can also be passed as keyword-arguments:
+
+```python
+from prometheus_client import Counter
+c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
+c.labels(method='get', endpoint='/').inc()
+c.labels(method='post', endpoint='/submit').inc()
+```
+
+Metrics with labels are not initialized when declared, because the client can't
+know what values the label can have. It is recommended to initialize the label
+values by calling the `.labels()` method alone:
+
+```python
+from prometheus_client import Counter
+c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
+c.labels('get', '/')
+c.labels('post', '/submit')
+```
\ No newline at end of file
diff --git a/docs/content/instrumenting/summary.md b/docs/content/instrumenting/summary.md
new file mode 100644
index 00000000..fa407496
--- /dev/null
+++ b/docs/content/instrumenting/summary.md
@@ -0,0 +1,25 @@
+---
+title: Summary
+weight: 3
+---
+
+Summaries track the size and number of events.
+
+```python
+from prometheus_client import Summary
+s = Summary('request_latency_seconds', 'Description of summary')
+s.observe(4.7) # Observe 4.7 (seconds in this case)
+```
+
+There are utilities for timing code:
+
+```python
+@s.time()
+def f():
+ pass
+
+with s.time():
+ pass
+```
+
+The Python client doesn't store or expose quantile information at this time.
\ No newline at end of file
diff --git a/docs/content/multiprocess/_index.md b/docs/content/multiprocess/_index.md
new file mode 100644
index 00000000..33507cd9
--- /dev/null
+++ b/docs/content/multiprocess/_index.md
@@ -0,0 +1,95 @@
+---
+title: Multiprocess Mode
+weight: 5
+---
+
+Prometheus client libraries presume a threaded model, where metrics are shared
+across workers. This doesn't work so well for languages such as Python where
+it's common to have processes rather than threads to handle large workloads.
+
+To handle this the client library can be put in multiprocess mode.
+This comes with a number of limitations:
+
+- Registries can not be used as normal, all instantiated metrics are exported
+ - Registering metrics to a registry later used by a `MultiProcessCollector`
+ may cause duplicate metrics to be exported
+- Custom collectors do not work (e.g. cpu and memory metrics)
+- Gauges cannot use `set_function`
+- Info and Enum metrics do not work
+- The pushgateway cannot be used
+- Gauges cannot use the `pid` label
+- Exemplars are not supported
+- Remove and Clear of labels are currently not supported in multiprocess mode.
+
+There's several steps to getting this working:
+
+**1. Deployment**:
+
+The `PROMETHEUS_MULTIPROC_DIR` environment variable must be set to a directory
+that the client library can use for metrics. This directory must be wiped
+between process/Gunicorn runs (before startup is recommended).
+
+This environment variable should be set from a start-up shell script,
+and not directly from Python (otherwise it may not propagate to child processes).
+
+**2. Metrics collector**:
+
+The application must initialize a new `CollectorRegistry`, and store the
+multi-process collector inside. It is a best practice to create this registry
+inside the context of a request to avoid metrics registering themselves to a
+collector used by a `MultiProcessCollector`. If a registry with metrics
+registered is used by a `MultiProcessCollector` duplicate metrics may be
+exported, one for multiprocess, and one for the process serving the request.
+
+```python
+from prometheus_client import multiprocess
+from prometheus_client import generate_latest, CollectorRegistry, CONTENT_TYPE_LATEST, Counter
+
+MY_COUNTER = Counter('my_counter', 'Description of my counter')
+
+# Expose metrics.
+def app(environ, start_response):
+ registry = CollectorRegistry()
+ multiprocess.MultiProcessCollector(registry)
+ data = generate_latest(registry)
+ status = '200 OK'
+ response_headers = [
+ ('Content-type', CONTENT_TYPE_LATEST),
+ ('Content-Length', str(len(data)))
+ ]
+ start_response(status, response_headers)
+ return iter([data])
+```
+
+**3. Gunicorn configuration**:
+
+The `gunicorn` configuration file needs to include the following function:
+
+```python
+from prometheus_client import multiprocess
+
+def child_exit(server, worker):
+ multiprocess.mark_process_dead(worker.pid)
+```
+
+**4. Metrics tuning (Gauge)**:
+
+When `Gauge`s are used in multiprocess applications,
+you must decide how to handle the metrics reported by each process.
+Gauges have several modes they can run in, which can be selected with the `multiprocess_mode` parameter.
+
+- 'all': Default. Return a timeseries per process (alive or dead), labelled by the process's `pid` (the label is added internally).
+- 'min': Return a single timeseries that is the minimum of the values of all processes (alive or dead).
+- 'max': Return a single timeseries that is the maximum of the values of all processes (alive or dead).
+- 'sum': Return a single timeseries that is the sum of the values of all processes (alive or dead).
+- 'mostrecent': Return a single timeseries that is the most recent value among all processes (alive or dead).
+
+Prepend 'live' to the beginning of the mode to return the same result but only considering living processes
+(e.g., 'liveall, 'livesum', 'livemax', 'livemin', 'livemostrecent').
+
+```python
+from prometheus_client import Gauge
+
+# Example gauge
+IN_PROGRESS = Gauge("inprogress_requests", "help", multiprocess_mode='livesum')
+```
diff --git a/docs/content/parser/_index.md b/docs/content/parser/_index.md
new file mode 100644
index 00000000..19f3b0e2
--- /dev/null
+++ b/docs/content/parser/_index.md
@@ -0,0 +1,16 @@
+---
+title: Parser
+weight: 6
+---
+
+The Python client supports parsing the Prometheus text format.
+This is intended for advanced use cases where you have servers
+exposing Prometheus metrics and need to get them into some other
+system.
+
+```python
+from prometheus_client.parser import text_string_to_metric_families
+for family in text_string_to_metric_families(u"my_gauge 1.0\n"):
+ for sample in family.samples:
+ print("Name: {0} Labels: {1} Value: {2}".format(*sample))
+```
\ No newline at end of file
diff --git a/docs/content/restricted-registry/_index.md b/docs/content/restricted-registry/_index.md
new file mode 100644
index 00000000..49b36e15
--- /dev/null
+++ b/docs/content/restricted-registry/_index.md
@@ -0,0 +1,29 @@
+---
+title: Restricted registry
+weight: 7
+---
+
+Registries support restriction to only return specific metrics.
+If you’re using the built-in HTTP server, you can use the GET parameter "name[]", since it’s an array it can be used multiple times.
+If you’re directly using `generate_latest`, you can use the function `restricted_registry()`.
+
+```python
+curl --get --data-urlencode "name[]=python_gc_objects_collected_total" --data-urlencode "name[]=python_info" http://127.0.0.1:9200/metrics
+```
+
+```python
+from prometheus_client import generate_latest
+
+generate_latest(REGISTRY.restricted_registry(['python_gc_objects_collected_total', 'python_info']))
+```
+
+```python
+# HELP python_info Python platform information
+# TYPE python_info gauge
+python_info{implementation="CPython",major="3",minor="9",patchlevel="3",version="3.9.3"} 1.0
+# HELP python_gc_objects_collected_total Objects collected during gc
+# TYPE python_gc_objects_collected_total counter
+python_gc_objects_collected_total{generation="0"} 73129.0
+python_gc_objects_collected_total{generation="1"} 8594.0
+python_gc_objects_collected_total{generation="2"} 296.0
+```
\ No newline at end of file
diff --git a/docs/data/menu/extra.yaml b/docs/data/menu/extra.yaml
new file mode 100644
index 00000000..7ee8af70
--- /dev/null
+++ b/docs/data/menu/extra.yaml
@@ -0,0 +1,6 @@
+---
+header:
+ - name: GitHub
+ ref: https://github.com/prometheus/client_python
+ icon: gdoc_github
+ external: true
diff --git a/docs/data/menu/more.yaml b/docs/data/menu/more.yaml
new file mode 100644
index 00000000..ea3b8617
--- /dev/null
+++ b/docs/data/menu/more.yaml
@@ -0,0 +1,14 @@
+---
+more:
+ - name: Releases
+ ref: "https://github.com/prometheus/client_python/releases"
+ external: true
+ icon: "gdoc_download"
+ - name: GitHub
+ ref: "https://github.com/prometheus/client_python"
+ external: true
+ icon: "gdoc_github"
+ - name: PyPi
+ ref: "https://pypi.python.org/pypi/prometheus_client"
+ icon: "gdoc_link"
+ external: true
diff --git a/docs/hugo.toml b/docs/hugo.toml
new file mode 100644
index 00000000..9e3e6037
--- /dev/null
+++ b/docs/hugo.toml
@@ -0,0 +1,119 @@
+baseURL = "http://localhost"
+languageCode = 'en-us'
+title = "client_python"
+theme = "hugo-geekdoc"
+
+pluralizeListTitles = false
+
+# Required to get well formatted code blocks
+pygmentsUseClasses = true
+pygmentsCodeFences = true
+disablePathToLower = true
+enableGitInfo = true
+
+# Required if you want to render robots.txt template
+enableRobotsTXT = true
+
+[markup]
+ [markup.goldmark.renderer]
+ # Needed for mermaid shortcode or when nesting shortcodes (e.g. img within
+ # columns or tabs)
+ unsafe = true
+ [markup.tableOfContents]
+ startLevel = 1
+ endLevel = 9
+ [markup.highlight]
+ style = 'solarized-dark'
+
+[taxonomies]
+ tag = "tags"
+
+[params]
+# (Optional, default 6) Set how many table of contents levels to be showed on page.
+# Use false to hide ToC, note that 0 will default to 6 (https://gohugo.io/functions/default/)
+# You can also specify this parameter per page in front matter.
+geekdocToC = 3
+
+# (Optional, default static/brand.svg) Set the path to a logo for the Geekdoc
+# relative to your 'static/' folder.
+geekdocLogo = "brand.svg"
+
+# (Optional, default false) Render menu from data file in 'data/menu/main.yaml'.
+# See also https://geekdocs.de/usage/menus/#bundle-menu.
+geekdocMenuBundle = false
+
+# (Optional, default false) Collapse all menu entries, can not be overwritten
+# per page if enabled. Can be enabled per page via 'geekdocCollapseSection'.
+geekdocCollapseAllSections = false
+
+# (Optional, default true) Show page navigation links at the bottom of each docs page.
+geekdocNextPrev = true
+
+# (Optional, default true) Show a breadcrumb navigation bar at the top of each docs page.
+# You can also specify this parameter per page in front matter.
+geekdocBreadcrumb = true
+
+# (Optional, default none) Set source repository location. Used for 'Edit page' links.
+# You can also specify this parameter per page in front matter.
+geekdocRepo = "https://github.com/prometheus/client_python"
+
+# (Optional, default none) Enable 'Edit page' links. Requires 'geekdocRepo' param
+# and the path must point to the parent directory of the 'content' folder.
+# You can also specify this parameter per page in front matter.
+geekdocEditPath = "edit/master/docs"
+
+# (Optional, default false) Show last modification date of the page in the header.
+# Keep in mind that last modification date works best if `enableGitInfo` is set to true.
+geekdocPageLastmod = true
+
+# (Optional, default true) Enables search function with flexsearch.
+# Index is built on the fly and might slow down your website.
+geekdocSearch = true
+
+# (Optional, default false) Display search results with the parent folder as prefix. This
+# option allows you to distinguish between files with the same name in different folders.
+# NOTE: This parameter only applies when 'geekdocSearch = true'.
+geekdocSearchShowParent = true
+
+# (Optional, default true) Add an anchor link to headlines.
+geekdocAnchor = true
+
+# (Optional, default true) Copy anchor url to clipboard on click.
+geekdocAnchorCopy = true
+
+# (Optional, default true) Enable or disable image lazy loading for images rendered
+# by the 'img' shortcode.
+geekdocImageLazyLoading = true
+
+# (Optional, default false) Set HTMl to .Site.Home.Permalink if enabled. It might be required
+# if a subdirectory is used within Hugos BaseURL.
+# See https://developer.mozilla.org/de/docs/Web/HTML/Element/base.
+geekdocOverwriteHTMLBase = false
+
+# (Optional, default true) Enable or disable the JavaScript based color theme toggle switch. The CSS based
+# user preference mode still works.
+geekdocDarkModeToggle = true
+
+# (Optional, default false) Auto-decrease brightness of images and add a slightly grayscale to avoid
+# bright spots while using the dark mode.
+geekdocDarkModeDim = false
+
+# (Optional, default false) Enforce code blocks to always use the dark color theme.
+geekdocDarkModeCode = false
+
+# (Optional, default true) Display a "Back to top" link in the site footer.
+geekdocBackToTop = true
+
+# (Optional, default false) Enable or disable adding tags for post pages automatically to the navigation sidebar.
+geekdocTagsToMenu = true
+
+# (Optional, default 'title') Configure how to sort file-tree menu entries. Possible options are 'title', 'linktitle',
+# 'date', 'publishdate', 'expirydate' or 'lastmod'. Every option can be used with a reverse modifier as well
+# e.g. 'title_reverse'.
+geekdocFileTreeSortBy = "title"
+
+# (Optional, default none) Adds a "Content licensed under " line to the footer.
+# Could be used if you want to define a default license for your content.
+[params.geekdocContentLicense]
+ name = "Apache License 2.0"
+ link = "https://github.com/prometheus/client_python/blob/master/LICENSE"
diff --git a/docs/static/.gitignore b/docs/static/.gitignore
new file mode 100644
index 00000000..eedd89b4
--- /dev/null
+++ b/docs/static/.gitignore
@@ -0,0 +1 @@
+api
diff --git a/docs/static/brand.svg b/docs/static/brand.svg
new file mode 100644
index 00000000..5c51f66d
--- /dev/null
+++ b/docs/static/brand.svg
@@ -0,0 +1,50 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/static/custom.css b/docs/static/custom.css
new file mode 100644
index 00000000..41ab7c56
--- /dev/null
+++ b/docs/static/custom.css
@@ -0,0 +1,39 @@
+/*
+ * Didn't find much time to create a theme yet,
+ * so there are just a few non-default settings for now.
+ */
+:root,
+:root[color-theme="light"] {
+ --header-background: #222222;
+ --footer-background: #e6522c;
+ --footer-link-color: #ffffff;
+ --footer-link-color-visited: #ffffff;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ --header-background: #222222;
+ --footer-background: #e6522c;
+ --footer-link-color: #ffffff;
+ --footer-link-color-visited: #ffffff;
+ }
+}
+
+:root[color-theme="dark"]
+{
+ --header-background: #111c24;
+ --body-background: #1f1f21;
+ --footer-background: #e6522c;
+ --footer-link-color: #ffffff;
+ --footer-link-color-visited: #ffffff;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --header-background: #111c24;
+ --body-background: #1f1f21;
+ --footer-background: #e6522c;
+ --footer-link-color: #ffffff;
+ --footer-link-color-visited: #ffffff;
+ }
+}
diff --git a/docs/static/favicon/favicon-16x16.png b/docs/static/favicon/favicon-16x16.png
new file mode 100644
index 00000000..7a8cc581
Binary files /dev/null and b/docs/static/favicon/favicon-16x16.png differ
diff --git a/docs/static/favicon/favicon-32x32.png b/docs/static/favicon/favicon-32x32.png
new file mode 100644
index 00000000..7d5a3ae3
Binary files /dev/null and b/docs/static/favicon/favicon-32x32.png differ
diff --git a/docs/static/favicon/favicon.ico b/docs/static/favicon/favicon.ico
new file mode 100644
index 00000000..34bd1fbf
Binary files /dev/null and b/docs/static/favicon/favicon.ico differ
diff --git a/docs/static/favicon/favicon.svg b/docs/static/favicon/favicon.svg
new file mode 100644
index 00000000..5c51f66d
--- /dev/null
+++ b/docs/static/favicon/favicon.svg
@@ -0,0 +1,50 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/themes/hugo-geekdoc/.nvmrc b/docs/themes/hugo-geekdoc/.nvmrc
new file mode 100644
index 00000000..b009dfb9
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/.nvmrc
@@ -0,0 +1 @@
+lts/*
diff --git a/docs/themes/hugo-geekdoc/LICENSE b/docs/themes/hugo-geekdoc/LICENSE
new file mode 100644
index 00000000..3812eb46
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Robert Kaussow
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/docs/themes/hugo-geekdoc/README.md b/docs/themes/hugo-geekdoc/README.md
new file mode 100644
index 00000000..b03365fb
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/README.md
@@ -0,0 +1,46 @@
+# Geekdoc
+
+[](https://ci.thegeeklab.de/repos/thegeeklab/hugo-geekdoc)
+[](https://gohugo.io)
+[](https://github.com/thegeeklab/hugo-geekdoc/releases/latest)
+[](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors)
+[](https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE)
+
+Geekdoc is a simple Hugo theme for documentations. It is intentionally designed as a fast and lean theme and may not fit the requirements of complex projects. If a more feature-complete theme is required there are a lot of good alternatives out there. You can find a demo and the full documentation at [https://geekdocs.de](https://geekdocs.de).
+
+
+
+## Build and release process
+
+This theme is subject to a CI driven build and release process common for software development. During the release build, all necessary assets are automatically built by [webpack](https://webpack.js.org/) and bundled in a release tarball. You can download the latest release from the GitHub [release page](https://github.com/thegeeklab/hugo-geekdoc/releases).
+
+Due to the fact that `webpack` and `npm scripts` are used as pre-processors, the theme cannot be used from the main branch by default. If you want to use the theme from a cloned branch instead of a release tarball you'll need to install `webpack` locally and run the build script once to create all required assets.
+
+```shell
+# install required packages from package.json
+npm install
+
+# run the build script to build required assets
+npm run build
+
+# build release tarball
+npm run pack
+```
+
+See the [Getting Started Guide](https://geekdocs.de/usage/getting-started/) for details about the different setup options.
+
+## Contributors
+
+Special thanks to all [contributors](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors). If you would like to contribute, please see the [instructions](https://github.com/thegeeklab/hugo-geekdoc/blob/main/CONTRIBUTING.md).
+
+Geekdoc is inspired and partially based on the [hugo-book](https://github.com/alex-shpak/hugo-book) theme, thanks [Alex Shpak](https://github.com/alex-shpak/) for your work.
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE) file for details.
+
+The used SVG icons and generated icon fonts are licensed under the license of the respective icon pack:
+
+- Font Awesome: [CC BY 4.0 License](https://github.com/FortAwesome/Font-Awesome#license)
+- IcoMoon Free Pack: [GPL/CC BY 4.0](https://icomoon.io/#icons-icomoon)
+- Material Icons: [Apache License 2.0](https://github.com/google/material-design-icons/blob/main/LICENSE)
diff --git a/docs/themes/hugo-geekdoc/VERSION b/docs/themes/hugo-geekdoc/VERSION
new file mode 100644
index 00000000..2e7bd910
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/VERSION
@@ -0,0 +1 @@
+v1.5.0
diff --git a/docs/themes/hugo-geekdoc/archetypes/docs.md b/docs/themes/hugo-geekdoc/archetypes/docs.md
new file mode 100644
index 00000000..aa0d88f7
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/archetypes/docs.md
@@ -0,0 +1,7 @@
+---
+title: "{{ .Name | humanize | title }}"
+weight: 1
+# geekdocFlatSection: false
+# geekdocToc: 6
+# geekdocHidden: false
+---
diff --git a/docs/themes/hugo-geekdoc/archetypes/posts.md b/docs/themes/hugo-geekdoc/archetypes/posts.md
new file mode 100644
index 00000000..fdccff8a
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/archetypes/posts.md
@@ -0,0 +1,4 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+date: {{ .Date }}
+---
diff --git a/docs/themes/hugo-geekdoc/assets/search/config.json b/docs/themes/hugo-geekdoc/assets/search/config.json
new file mode 100644
index 00000000..1a5582a2
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/assets/search/config.json
@@ -0,0 +1,8 @@
+{{- $searchDataFile := printf "search/%s.data.json" .Language.Lang -}}
+{{- $searchData := resources.Get "search/data.json" | resources.ExecuteAsTemplate $searchDataFile . | resources.Minify -}}
+{
+ "dataFile": {{ $searchData.RelPermalink | jsonify }},
+ "indexConfig": {{ .Site.Params.geekdocSearchConfig | jsonify }},
+ "showParent": {{ if .Site.Params.geekdocSearchShowParent }}true{{ else }}false{{ end }},
+ "showDescription": {{ if .Site.Params.geekdocSearchshowDescription }}true{{ else }}false{{ end }}
+}
diff --git a/docs/themes/hugo-geekdoc/assets/search/data.json b/docs/themes/hugo-geekdoc/assets/search/data.json
new file mode 100644
index 00000000..f1c0e804
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/assets/search/data.json
@@ -0,0 +1,13 @@
+[
+ {{ range $index, $page := (where .Site.Pages "Params.geekdocProtected" "ne" true) }}
+ {{ if ne $index 0 }},{{ end }}
+ {
+ "id": {{ $index }},
+ "href": "{{ $page.RelPermalink }}",
+ "title": {{ (partial "utils/title" $page) | jsonify }},
+ "parent": {{ with $page.Parent }}{{ (partial "utils/title" .) | jsonify }}{{ else }}""{{ end }},
+ "content": {{ $page.Plain | jsonify }},
+ "description": {{ $page.Summary | plainify | jsonify }}
+ }
+ {{ end }}
+]
diff --git a/docs/themes/hugo-geekdoc/assets/sprites/geekdoc.svg b/docs/themes/hugo-geekdoc/assets/sprites/geekdoc.svg
new file mode 100644
index 00000000..4f3cfd29
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/assets/sprites/geekdoc.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/themes/hugo-geekdoc/data/assets.json b/docs/themes/hugo-geekdoc/data/assets.json
new file mode 100644
index 00000000..29e02b0d
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/data/assets.json
@@ -0,0 +1,454 @@
+{
+ "main.js": {
+ "src": "js/main-2e274343.bundle.min.js",
+ "integrity": "sha512-Atj/tzetkQMROhw9Vq9Eg5cDA5Zw0j04fDL3AZNrqod5fQqakcgtNBSMvmEIOLPq6YiWX5Qu9x89m71mc/YjmA=="
+ },
+ "colortheme.js": {
+ "src": "js/colortheme-01ea3db1.bundle.min.js",
+ "integrity": "sha512-HC/hlLHETnfrQ4/mC/m71pSucEoWaEohD68gPOYu1LV31pQJoTNAYrmugK/FE9cHB9S9k+lL6yMJY1wd9/KGaQ=="
+ },
+ "mermaid.js": {
+ "src": "js/mermaid-c274c389.bundle.min.js",
+ "integrity": "sha512-EXY62vn1+kNduAXY+yQZi81u98IWTkjuvEj55Ge8rAIKWEUG/dddkwfkT+Nc0yoje7w27Ff1WJ9Wy8DYctmq4A=="
+ },
+ "katex.js": {
+ "src": "js/katex-13a419d8.bundle.min.js",
+ "integrity": "sha512-6MAwLoWro9jZYvZmrGTfM5oe46Ycf/V5eXGyEAIwvuxxxmbqJttAxkkJE/00mOO11Fb21BmI1udcGRb8CbFpWw=="
+ },
+ "search.js": {
+ "src": "js/search-16a110ff.bundle.min.js",
+ "integrity": "sha512-3GlBrQ51hc6WxYIDcSvYztzli9Qk6DwulVxXhZHNP/oSaK0kulRkdrMSunRTd1eb1SJE08VI/YLylmxZ5SV0hw=="
+ },
+ "js/664-ed5252a5.chunk.min.js": {
+ "src": "js/664-ed5252a5.chunk.min.js",
+ "integrity": "sha512-9omg+hspX9YOzPdRzN6S5swqg1DLRMxHRUyDd6e6ECkkn6h7YrLzLrdRbbtIk0gAsnpzT9luk7XFvAO2wsrXZg=="
+ },
+ "js/485-3b9fa0c4.chunk.min.js": {
+ "src": "js/485-3b9fa0c4.chunk.min.js",
+ "integrity": "sha512-ho38krMSQfeNoX46V7dFr28Hnn2bOpO9QWLB0sxZuX2GP6xwemk7cxLiGE9dM3qYGOlrU0eLqRvzPeoxFsF2Jw=="
+ },
+ "js/417-65958f5a.chunk.min.js": {
+ "src": "js/417-65958f5a.chunk.min.js",
+ "integrity": "sha512-bjVOwri6TCc8gl4cHEshCj9dWqVYQis5TVOaQHyWntIeK1WbiB1Q2/AcHlnTT4ROpK2uh5IiyRjUkVJvXB/PVw=="
+ },
+ "js/978-382bed37.chunk.min.js": {
+ "src": "js/978-382bed37.chunk.min.js",
+ "integrity": "sha512-+2O8x3RNKRlmtGca+J3x+K/62n6rKSS3qIPwtm2vWRKIccYHA5LT9ENlSyvyh4LpaHwd2Dojq6DVGZIAT6Wnhg=="
+ },
+ "js/244-c41eb325.chunk.min.js": {
+ "src": "js/244-c41eb325.chunk.min.js",
+ "integrity": "sha512-2A6PJtt6+TmkDMr7/jIJ07MIFy2ipT58aSUfZAeIeCi6dXTeoG/5fTT1zLZecZh5GsXGMpNyqKkqZqVwbWzc0A=="
+ },
+ "js/354-5c1850f7.chunk.min.js": {
+ "src": "js/354-5c1850f7.chunk.min.js",
+ "integrity": "sha512-NvmJwSFujfF+S1n5AbVS+8QvypWYyMyQdui3bP/cMi1XUl+5AB1ou0O4yMC85o0JtZMDImZieFUYNoM8yTHKXw=="
+ },
+ "js/825-445b5bd5.chunk.min.js": {
+ "src": "js/825-445b5bd5.chunk.min.js",
+ "integrity": "sha512-NOqFursH5QZiDD7gx0AtWI1DckXYyBkJgt1Gv347PB5otVKbJWQ+s+xdx0GK3pVI2VK6mRCyjqapm3tnxxfEYg=="
+ },
+ "js/632-7a25d3c6.chunk.min.js": {
+ "src": "js/632-7a25d3c6.chunk.min.js",
+ "integrity": "sha512-PFbvM8X5viF9mjaWocBoQgeM+KSuznQEEXUtaqbdQSEedobqaHx0Q3Dn9ZYj2d20GGy/HZ9wRSppFw5yTdzC1w=="
+ },
+ "js/545-bfa2b46e.chunk.min.js": {
+ "src": "js/545-bfa2b46e.chunk.min.js",
+ "integrity": "sha512-OnH4AMYVIBwonc5gh4Uzp45VbP5cDn/9zhXxDDSXHVNWrO3sHG1lA7yhXFEJ1mll+GGihSe1ev81h+SlQmg5Gg=="
+ },
+ "js/413-c02a8543.chunk.min.js": {
+ "src": "js/413-c02a8543.chunk.min.js",
+ "integrity": "sha512-Aa//KQ7fjdgdywKY9oeq2Uf6OHJp/pZ4B0qCFfF+YIuY2+3p4fVXVZN/gvxKle9CDDtfnqhZH2ni9o+Mgnv0Sg=="
+ },
+ "js/540-ae28fd42.chunk.min.js": {
+ "src": "js/540-ae28fd42.chunk.min.js",
+ "integrity": "sha512-0i637ntjWOeUhb3hduyWJNcs/m8Fqa1BHgX++swq8/3SspywUV77sZy4NmnazHVXoMg4BLu1RMr7caAb9iJu6A=="
+ },
+ "js/391-549a9d24.chunk.min.js": {
+ "src": "js/391-549a9d24.chunk.min.js",
+ "integrity": "sha512-aty4EWAqEyrg6iKWtLcl+5hBkQVP8ZffyeP1XlKEqRswjATpDiRpzpy+5sSTaMo+ZQuim88zQtIFcQQbrteoYA=="
+ },
+ "js/56-09931933.chunk.min.js": {
+ "src": "js/56-09931933.chunk.min.js",
+ "integrity": "sha512-RI+ajv2yYUjfck1pGhd31E+MmsEYcTuA7TGRp4vO9lC43SnWaNgSSwQnETy0SAX3oDBJai8sPcMX6+Mivtn6kw=="
+ },
+ "js/732-8e5770e7.chunk.min.js": {
+ "src": "js/732-8e5770e7.chunk.min.js",
+ "integrity": "sha512-02TlRFXaL+tKrGpT07d9ebyABzylckAdWZmay3gSfuF5RfN3sfwh7C/ljNbaPXqbfn0/GKOmdP81Mce4xPOp7g=="
+ },
+ "js/110-f4b990d9.chunk.min.js": {
+ "src": "js/110-f4b990d9.chunk.min.js",
+ "integrity": "sha512-W8OJfGeXIVhZ5HUkL/9zwaRBePcRIgqWFZSii2C6BOTBcO7rx8RyvwnGynRIB1fQ+xg5sqbuPpzDE1K0S4VjrQ=="
+ },
+ "js/237-c0a3f3fe.chunk.min.js": {
+ "src": "js/237-c0a3f3fe.chunk.min.js",
+ "integrity": "sha512-VBL69Cyj7saW+K5+F8gL24kjjjyp6l9BUV7Ombfmk7+sw3uIA3W2rmbkO4loKkrcTA3HyR5+6AZ6nAArWjFyfg=="
+ },
+ "js/691-2a6930fd.chunk.min.js": {
+ "src": "js/691-2a6930fd.chunk.min.js",
+ "integrity": "sha512-HMvZTv5pI+P3wyGu4E2zcg01INdohp+ttXix+3UUxm70Ejf/kUPqppdJ2gQrdlt/Woor96vt6cFGs7lPQlZagg=="
+ },
+ "js/383-e450e912.chunk.min.js": {
+ "src": "js/383-e450e912.chunk.min.js",
+ "integrity": "sha512-YTYkszF4ql46H1hY2ZixoxMez9Ep6Bh2KV8MHnkYJgeCpOq5lcrY8lITtc/6rTcn/6wppwPK/suPlLGqdCmLWg=="
+ },
+ "js/355-ef4f96e9.chunk.min.js": {
+ "src": "js/355-ef4f96e9.chunk.min.js",
+ "integrity": "sha512-JUcjY4KCeyRs+OWDes22CmbbqSU9uefYdcb0CpcCsYoU9Pw97xtYl137OV5RR2Vr+/6eGGQ2yiIX8BOp2tWDzw=="
+ },
+ "js/648-b5ba4bb4.chunk.min.js": {
+ "src": "js/648-b5ba4bb4.chunk.min.js",
+ "integrity": "sha512-mckLNYEm3NMnZ1DnAafEbWgCshyuvNssKAU9sxBM5E/EXCeCwWmnP7RQYujTFA35AXIRq0d9Q7Ux2AsfWhSDMQ=="
+ },
+ "js/357-e9bfa102.chunk.min.js": {
+ "src": "js/357-e9bfa102.chunk.min.js",
+ "integrity": "sha512-9aMlQY3ZQwKrMFgRIOGDPHHVFYMCHJbsybK0ZjghpkwKxiP7+ETshuuf0ggeaGus907OqGANF9qTHX+zT15NhA=="
+ },
+ "js/410-ec3f5ed1.chunk.min.js": {
+ "src": "js/410-ec3f5ed1.chunk.min.js",
+ "integrity": "sha512-Z1s/OadNKjsxftNbZ9+MLokSdlRkWBTcJwi+jQd9Q87f3ZHocWohb8XXQCER07Fxqq8Sxy/2eVso8UKcuX6CIw=="
+ },
+ "js/175-405e6b1c.chunk.min.js": {
+ "src": "js/175-405e6b1c.chunk.min.js",
+ "integrity": "sha512-ddZGV8HI0FH+Y7AnWdgDELXsIiAnkk488LCSdmnHsGqq65JPy0HLT6eXudW766K0XKTUmn8uGog6NvH1FCd4JQ=="
+ },
+ "js/130-3b252fb9.chunk.min.js": {
+ "src": "js/130-3b252fb9.chunk.min.js",
+ "integrity": "sha512-JSvuAP7SZ8ndEm18NeuU27acjLOIOmZXB4TG79Sk0JdFIyn2YgidieBBcbIjn85udp4o4IjANY161KygULGiHQ=="
+ },
+ "js/12-0b8427d1.chunk.min.js": {
+ "src": "js/12-0b8427d1.chunk.min.js",
+ "integrity": "sha512-EpyKr8i98wqi4fpxW5ux156KszFLW9Iusw7MUMPf26kso3TPEKJYn28wY/giEeqsRL7y7EoX5xQR5O94VU1s8Q=="
+ },
+ "js/890-c9907c95.chunk.min.js": {
+ "src": "js/890-c9907c95.chunk.min.js",
+ "integrity": "sha512-gD2gqeomVVlkJ6wgB1VcUPizRgyG4JdQJ0t98yt9pVb07uzkhAAhKSddzxP/OF3tUA2bYZHrUYcgEkDAX5JOjQ=="
+ },
+ "js/452-e65d6d68.chunk.min.js": {
+ "src": "js/452-e65d6d68.chunk.min.js",
+ "integrity": "sha512-oOJ9nLMs4Ih5X9kyj5828RYSUg+7Wzcz4QEhURKPZWO1F1dSFNfmih2LJcFvjSdNp8wDepvAUQcQLDz3F7MX9g=="
+ },
+ "js/723-47eb515a.chunk.min.js": {
+ "src": "js/723-47eb515a.chunk.min.js",
+ "integrity": "sha512-W5+LIxRrc4yIVvFTgX3mx/Wd1K/HPhtr1j6IanCDprpeNAl2if5eMlCDZDhUJYZSm7ta4s4lb+IkdGaSf7EEKg=="
+ },
+ "js/720-970f726e.chunk.min.js": {
+ "src": "js/720-970f726e.chunk.min.js",
+ "integrity": "sha512-KZoim0oHUzo3JWb5J9AV6RNVm43jnQJyRBbV8gYTS6te6+h4VYg62lbjrapFwBQmHOMkcyLCp1dH2PqHvL36Qg=="
+ },
+ "js/387-3546ecdc.chunk.min.js": {
+ "src": "js/387-3546ecdc.chunk.min.js",
+ "integrity": "sha512-XA2Opiddehmv/Po1naDCYg2seMBBqYOzJbDT1WTvT8gLNVuQaI61Fw1hbCxIIOz2t/5LtnqErZc+tond4WuO5Q=="
+ },
+ "js/164-f339d58d.chunk.min.js": {
+ "src": "js/164-f339d58d.chunk.min.js",
+ "integrity": "sha512-oXaJMX/nm2r1p0EZhWyKp58KR6VwF06WafroIHmEiTMD7tDT+KfXf1Ryk+RopXvHx8swAt+DmOcWvsYjBc8+DQ=="
+ },
+ "js/731-6d56a3c0.chunk.min.js": {
+ "src": "js/731-6d56a3c0.chunk.min.js",
+ "integrity": "sha512-qWHhPkXehCCi7T5iVy0ZcC3CcJ848ZMAWDEYV13j2Izy4DJpnO4J0P1JBA8XawQ3Xxu0UVDzBjvm35MKadF6Zw=="
+ },
+ "js/567-6c3220fd.chunk.min.js": {
+ "src": "js/567-6c3220fd.chunk.min.js",
+ "integrity": "sha512-U8xZDwmMJQAUM8v4ZXJf1167FWhTvq8vrp1GagOOoivFXNw2IdO0mWRhb9NnohVouf+dooRurCBM+YUYfJEjfg=="
+ },
+ "js/165-06872da1.chunk.min.js": {
+ "src": "js/165-06872da1.chunk.min.js",
+ "integrity": "sha512-pgEr6k7AOnuHgCibSGCs8oB66vecF5ww5apTymYFD+z1YYCLgtHzS7uacJsMPfrCxheMOg/ng2bfGzc9cS2C3A=="
+ },
+ "js/240-cd383fa4.chunk.min.js": {
+ "src": "js/240-cd383fa4.chunk.min.js",
+ "integrity": "sha512-BUDkwZXONurj953rNzKsuDb6BEIFBQqoJ6FvfHZzPvnJpeMeRnxRrxAIAWAf1+5OXH29pUledg7xoZeImUIZKQ=="
+ },
+ "js/758-5696af5a.chunk.min.js": {
+ "src": "js/758-5696af5a.chunk.min.js",
+ "integrity": "sha512-Uqo6/Ld+ah7lbiY+GR1vMzOFsAaJ+CVyEgzkRICSKa7PwMvyWQ/pjXZEs0k+yrl3bOpX5ma4TOa7MVn7vtOMLg=="
+ },
+ "favicon/apple-touch-startup-image-2048x2732.png": {
+ "src": "favicon/apple-touch-startup-image-2048x2732.png",
+ "integrity": "sha512-pp/8QkfwltmJfJZv6lzhl9bbE+0ltO1lcpXR3432kiV2VCl1SXOiTiJYzU/lVmTO1wMrdyFwHdk0C0ZPauVmUg=="
+ },
+ "main.scss": {
+ "src": "main-b53472e8.min.css",
+ "integrity": "sha512-OdpB6Sg1KAfyLTE+HfAyWywLvzCU8lrsfVurFgj+rCZ3fwMevRyqu6WsHRHmoh3+OJv8RCuy2xbdqFZtSP7OLA=="
+ },
+ "favicon/apple-touch-startup-image-2732x2048.png": {
+ "src": "favicon/apple-touch-startup-image-2732x2048.png",
+ "integrity": "sha512-DOw5FcezHTkJ2dDT8agLZlIfrNZoxc0/OTlrkmuYgpRJiIkJykxAYQed0Ysu/MBkfwe6lWDydhlpV8oomWMKgw=="
+ },
+ "favicon/apple-touch-startup-image-1668x2388.png": {
+ "src": "favicon/apple-touch-startup-image-1668x2388.png",
+ "integrity": "sha512-Stx19Yj7N6TXbMiFMq03kLQYs1X+ft6zmpwVa/+06q8I48P+8dG64MnC8zvl0PqzYWGwcBtCa8m+/qy5JQHzmw=="
+ },
+ "favicon/apple-touch-startup-image-1668x2224.png": {
+ "src": "favicon/apple-touch-startup-image-1668x2224.png",
+ "integrity": "sha512-OJnVL7cFjpYgoqph0ZAAZ0bQMeHZHyYzeasV314vTyarpeyVDZuw0j/U2F/7ldxgFVP+Z67RNfLGfSr6SKqujw=="
+ },
+ "favicon/apple-touch-startup-image-2224x1668.png": {
+ "src": "favicon/apple-touch-startup-image-2224x1668.png",
+ "integrity": "sha512-h86d25uMsQo1wqWrc0Bm7hwQPx1/WMpIcuFXq6TV4v7QLix8jaBeXjCz6d/JG9dQVqp0rJj2L2Koh9KR4iLlbQ=="
+ },
+ "favicon/apple-touch-startup-image-2388x1668.png": {
+ "src": "favicon/apple-touch-startup-image-2388x1668.png",
+ "integrity": "sha512-HrLClFRnn0TKngyeMONGPw8WFltiAd/+456Z2w+/tRYlhblrxfNxddoacMhAfywJuZL2bnMrDFxgIeisKV7UZg=="
+ },
+ "favicon/apple-touch-startup-image-1640x2160.png": {
+ "src": "favicon/apple-touch-startup-image-1640x2160.png",
+ "integrity": "sha512-bkGRXPNafzTvHm7iqK90kmtvdUIg1davqSECk72QWcc8KQhB58+j6Y/Lsv4PNhuki/3CafltGYPwq5DC/uFwLg=="
+ },
+ "favicon/apple-touch-startup-image-1620x2160.png": {
+ "src": "favicon/apple-touch-startup-image-1620x2160.png",
+ "integrity": "sha512-a52rXNm6ZAK3hBxTW9ySrYEX76I11+P20QU4eS1spuSHH9byqr82n2C2vWsB3ASOvJgF6L9X2m1gTfcezcWa2Q=="
+ },
+ "favicon/apple-touch-startup-image-2160x1640.png": {
+ "src": "favicon/apple-touch-startup-image-2160x1640.png",
+ "integrity": "sha512-lAMwiXWTpWy3R8WXVK0Pxyfzh+nVf6TWxB1CS28nckPIvoJZ01UDW7MX15R6VJH4hC6b9yBwRFqgiWI3ey7XIg=="
+ },
+ "favicon/apple-touch-startup-image-2160x1620.png": {
+ "src": "favicon/apple-touch-startup-image-2160x1620.png",
+ "integrity": "sha512-q4BwNvR4nA/lX+O3hw5SAhDnyOAsxK2QbaUt0J2rBVr9nhewmvgyvPEQTt/rI2+v5Obt8ofbB1nKKTUKpCPpTQ=="
+ },
+ "favicon/apple-touch-startup-image-1536x2048.png": {
+ "src": "favicon/apple-touch-startup-image-1536x2048.png",
+ "integrity": "sha512-gvsMZlTvNSZUJ52q80FFfNk+oLaAw2w8EEcX3ns9QYdNJAhn51+VHnceIw49xiQpMZxu8djiEDhmGAbrnBc8Aw=="
+ },
+ "favicon/apple-touch-startup-image-2048x1536.png": {
+ "src": "favicon/apple-touch-startup-image-2048x1536.png",
+ "integrity": "sha512-HddG543jHxr+S6DljYFOj+mOrh5xQfIv+Ca2aCDuY+AU15vXWvuMeRAaNB5eGaXUA5ngSrGkPSR6cZItcipmFg=="
+ },
+ "favicon/apple-touch-startup-image-1488x2266.png": {
+ "src": "favicon/apple-touch-startup-image-1488x2266.png",
+ "integrity": "sha512-M+iU7dAuzTuuhlkFLwLOnkC/hsN6pFEuwngs+PmKEQeHnWw/nzIsfovwEjQTm5Bz7h/bbwaF8szZFHGh2lNl5A=="
+ },
+ "favicon/apple-touch-startup-image-2266x1488.png": {
+ "src": "favicon/apple-touch-startup-image-2266x1488.png",
+ "integrity": "sha512-SOCJUsMcfWiGiQFMdQ7lhUZrjio+/jwrHidpBmMZqxQL8TESi0ODeU3F1ARleaPF+rvjcWmpFpmFN7kn9tkaAA=="
+ },
+ "favicon/apple-touch-startup-image-1284x2778.png": {
+ "src": "favicon/apple-touch-startup-image-1284x2778.png",
+ "integrity": "sha512-HytWl/niNY0h8Z2g+lCOn7O9/fpBS+oPU73GnBNCd7CDwHs+IpzZ0duuRlKmfdH8x80y2bsK5DHcRDQo8TJOPQ=="
+ },
+ "favicon/apple-touch-startup-image-1290x2796.png": {
+ "src": "favicon/apple-touch-startup-image-1290x2796.png",
+ "integrity": "sha512-uE8D0pZL30x6zd3sq8tPPcmC6Q8g2dSrnypzZGllIkfSGVoj+tSEKcYrS+/L6DPM3jMuF69TNScufJtVA+Qupg=="
+ },
+ "favicon/apple-touch-startup-image-1242x2688.png": {
+ "src": "favicon/apple-touch-startup-image-1242x2688.png",
+ "integrity": "sha512-IR0rOpZn1Vs2fT7UavU7MA8D/PDGS7XmaTwkiPxLi3207GPDxZdQHIKA0vIJSodDGJT/ajON/zxDciq/6Jd00Q=="
+ },
+ "favicon/apple-touch-startup-image-1242x2208.png": {
+ "src": "favicon/apple-touch-startup-image-1242x2208.png",
+ "integrity": "sha512-V2CpCg23Xb5d0wHJS0dDPjXs9Mk2CxMOn2cx/b9zC2RWBR9QF/F33zI+MioRQ9RPqCZwt093erdAiEiOonDS3Q=="
+ },
+ "favicon/apple-touch-startup-image-2796x1290.png": {
+ "src": "favicon/apple-touch-startup-image-2796x1290.png",
+ "integrity": "sha512-Hn5Bsg7wYJhZhE+UmIMBS0lg+lHWjcrNjY/23Qxvk8keWq/D+LEz8UBA8+b9xaCF+HXo39l41keoix9bvg4zyg=="
+ },
+ "favicon/apple-touch-startup-image-2778x1284.png": {
+ "src": "favicon/apple-touch-startup-image-2778x1284.png",
+ "integrity": "sha512-CF8j/XPdlQUQHNjxGO59cS2GVyskflUEPnCqKOWellvVq+RdRa7r3952bNVlUrfzdCoaeszmZS4n71qn2ZTyTA=="
+ },
+ "favicon/apple-touch-startup-image-2208x1242.png": {
+ "src": "favicon/apple-touch-startup-image-2208x1242.png",
+ "integrity": "sha512-Ime4TqPHk2qrjA8eHM50as6Sgnlvn3pCkLlI1B/yBDvZ4CPWxDidSmWeJHeV//3dThozo95VllD1bvz/cw8gQA=="
+ },
+ "favicon/apple-touch-startup-image-1179x2556.png": {
+ "src": "favicon/apple-touch-startup-image-1179x2556.png",
+ "integrity": "sha512-CGw2nqsLTTrX3YjpHGuJD18Mv8tHySni96E6Z6pTGwfAKK1l6UCqFtbRlUZQ2MlN8vudm4aFifKtPDlFyyAOzw=="
+ },
+ "favicon/apple-touch-startup-image-1170x2532.png": {
+ "src": "favicon/apple-touch-startup-image-1170x2532.png",
+ "integrity": "sha512-Bctz35gi47GseEkA5EmsAVmtS60Vhlrc0czWW4UY0cQqIGO0VfoGvSXaccCNesY8VMgVWoZayLxcwrUWbUKK9A=="
+ },
+ "favicon/apple-touch-startup-image-2688x1242.png": {
+ "src": "favicon/apple-touch-startup-image-2688x1242.png",
+ "integrity": "sha512-ZamHO4IC0SZ5XhNCI0HaeGaKiDgLhuwWZ12z9Rt0auKt9bvtVucJgI74iAmRXE9zZNE5nmZwMuhajd+dzmZamg=="
+ },
+ "favicon/apple-touch-startup-image-1125x2436.png": {
+ "src": "favicon/apple-touch-startup-image-1125x2436.png",
+ "integrity": "sha512-FNQGGCfYgeFjeFzLFNmqcB9bcWaEX6rGk1bUS+oetvVQBU9iZ/YYp9go1A5oeifV1MMX290mlcDwG4i/mg2I0g=="
+ },
+ "favicon/favicon.ico": {
+ "src": "favicon/favicon.ico",
+ "integrity": "sha512-oyLtFbxhoEnH/aFDXDWkC+S1LT5M7VHeH+f+FOLsy8JzsswzGR0VkLu/BFvzyVQTzexmfNjP4ZFm6QJYW1/7hw=="
+ },
+ "favicon/apple-touch-startup-image-2556x1179.png": {
+ "src": "favicon/apple-touch-startup-image-2556x1179.png",
+ "integrity": "sha512-Jtknw0tI9ryKINVqgtOWLR8dZgc6cPhrh1XrDwQHRGvfdwTcU2/AGVr1w9mj59RZNnMZZgikpdW0ebZuUe4YjA=="
+ },
+ "favicon/apple-touch-startup-image-2532x1170.png": {
+ "src": "favicon/apple-touch-startup-image-2532x1170.png",
+ "integrity": "sha512-vAjXBduB/PLTvOwTsCf+VvkRq5PNhxCjDMJ408ul3wFjUb7owqU/LKspOtkNuxOE2H9u2aXqJhdcR61AUdeP8Q=="
+ },
+ "favicon/apple-touch-startup-image-2436x1125.png": {
+ "src": "favicon/apple-touch-startup-image-2436x1125.png",
+ "integrity": "sha512-yW+pbc/y6e4ZtL/PfbA77bs++nyHDjt2LewdNSgHoFytdO/0IzCi2th64HrqjkXAnwieqnqBIHOmfQDb6ntOxw=="
+ },
+ "favicon/apple-touch-icon-1024x1024.png": {
+ "src": "favicon/apple-touch-icon-1024x1024.png",
+ "integrity": "sha512-uNxs8UKFz57bkfl4uezhkIl4VfZIuSOV6lcaE/0VIYbx8hFZ7SJTShz9wiIzPMZsCSHKMY5P7uhr0FigLGD+3w=="
+ },
+ "katex.css": {
+ "src": "katex-a0da2a32.min.css",
+ "integrity": "sha512-dsM9rZ31dli/kG9VZShrbuMaNaj6t/aVT6/ZjfTuSGNp1r1EonVHHESDrKKHGbmYqs0HIUcnpWIOEqsoDlpdyw=="
+ },
+ "favicon/apple-touch-startup-image-828x1792.png": {
+ "src": "favicon/apple-touch-startup-image-828x1792.png",
+ "integrity": "sha512-lOKELuDZcqdtCvvU+wU4XbRSGVx4j5fXOViEIy8vJ/H/vad9Nb1HjXA517Mo2X3KE+xWpKBa7iaRKONe2NR77A=="
+ },
+ "favicon/apple-touch-startup-image-1792x828.png": {
+ "src": "favicon/apple-touch-startup-image-1792x828.png",
+ "integrity": "sha512-Q0rPW22UcOSrAk1Cc+VJElqo1FUOxN6M5yk6rr19l15aDfwMmlWVLVCEEuYr7YN9Yd7P6oFIP5krWpBwP8XevA=="
+ },
+ "favicon/apple-touch-startup-image-750x1334.png": {
+ "src": "favicon/apple-touch-startup-image-750x1334.png",
+ "integrity": "sha512-zFiwOUbcWZ5ZT6WIoo5JH5sBgNRKgaw+38nZ4INvrJksTXVYiTSNK+HI+g/fpjATMD3oIy3zRD1QD5MF0xcI+A=="
+ },
+ "favicon/apple-touch-startup-image-1334x750.png": {
+ "src": "favicon/apple-touch-startup-image-1334x750.png",
+ "integrity": "sha512-wS3VX86WIIMYLFcu6PTWwilPBtW2/eQgoFC4nUPbxOhA6tDCv0jXfLhpFBk0kEPvtFGqIzdMIwkhB3Q9z2WuEQ=="
+ },
+ "favicon/apple-touch-startup-image-640x1136.png": {
+ "src": "favicon/apple-touch-startup-image-640x1136.png",
+ "integrity": "sha512-Ol0z2NW7PjFrVwo5GQ0IolK6IsFJyji9biOIE7BW9wuid/H8VhMW6/j4Sxh9SZ/v0NEtQqaA5VOjvLT7hcpxVA=="
+ },
+ "favicon/apple-touch-startup-image-1136x640.png": {
+ "src": "favicon/apple-touch-startup-image-1136x640.png",
+ "integrity": "sha512-l7AF6JJHQNpeEOT32Tj+sZsyigN+FIer/RLxKqwLzXZ3cPMizSjmL5FjfoyZ7waJfDpxV448BWJcpObDEp2f0Q=="
+ },
+ "favicon/android-chrome-512x512.png": {
+ "src": "favicon/android-chrome-512x512.png",
+ "integrity": "sha512-XmRxXro8tWSW9pyhfNcuoIVqHqOHH051Lh8NpsR0bMMILrx4QSIGI+IOKo2DYafyJ32rRXQ9XapCUigUoU9lVA=="
+ },
+ "favicon/android-chrome-384x384.png": {
+ "src": "favicon/android-chrome-384x384.png",
+ "integrity": "sha512-aaWWtDDKoURtcZjVjuEygWnAX3JmiMIkzG2gw0e90QU2BBiMEFRh+Dq5lONs3NKviyhKrWjYXktnLzbBDgwYqw=="
+ },
+ "favicon/mstile-310x310.png": {
+ "src": "favicon/mstile-310x310.png",
+ "integrity": "sha512-0cJZvExwO4YX9shSiRIio61MHiRYzmd1ZKJcIuurb30a85VAebz64fGkg5WgaljhDufbzQV8juSMSMdjVU1PaQ=="
+ },
+ "favicon/android-chrome-256x256.png": {
+ "src": "favicon/android-chrome-256x256.png",
+ "integrity": "sha512-7K6tC2Nt0G4xGWOnXI0eHTnflCfBnmoZI+41wRXubcINCVj9zfE1urbpRvWXu+JEkyoD+/1i/SHKJvlj0V8Qng=="
+ },
+ "favicon/android-chrome-192x192.png": {
+ "src": "favicon/android-chrome-192x192.png",
+ "integrity": "sha512-vFuJFgoHAo1gYkmVDylyiAHTUEAzZWmusNxCf4BKZucXjB1O5WSNrnaDHd/P1U3If7pTDG3zM3R8xll9qn/TFw=="
+ },
+ "favicon/apple-touch-icon-167x167.png": {
+ "src": "favicon/apple-touch-icon-167x167.png",
+ "integrity": "sha512-n9IE0XrWkdUJCWDP+BXWGZ3f8YPWUt0j1YbpOql6ECHbBv94MqBZsCNgAAZcz2nlngn6B/VsLquKPF+C73uAaA=="
+ },
+ "favicon/apple-touch-icon-180x180.png": {
+ "src": "favicon/apple-touch-icon-180x180.png",
+ "integrity": "sha512-MOwxPnc3afecYk/ITIQPavTxfNlk68gSBXzbhrf+cYuXaXx+OKApfhsfT0MwS0RjFsi50lirbvtJyyWUce+AnA=="
+ },
+ "favicon/apple-touch-icon-precomposed.png": {
+ "src": "favicon/apple-touch-icon-precomposed.png",
+ "integrity": "sha512-MOwxPnc3afecYk/ITIQPavTxfNlk68gSBXzbhrf+cYuXaXx+OKApfhsfT0MwS0RjFsi50lirbvtJyyWUce+AnA=="
+ },
+ "favicon/apple-touch-icon.png": {
+ "src": "favicon/apple-touch-icon.png",
+ "integrity": "sha512-MOwxPnc3afecYk/ITIQPavTxfNlk68gSBXzbhrf+cYuXaXx+OKApfhsfT0MwS0RjFsi50lirbvtJyyWUce+AnA=="
+ },
+ "favicon/apple-touch-icon-152x152.png": {
+ "src": "favicon/apple-touch-icon-152x152.png",
+ "integrity": "sha512-Tl7OztU9EPEmqAB5g1fZbDfJILIFGGRYoXVRLmBli4G/kDRcZMhsZPEpwjcaElSsZ6Vf+GOBX5w+y/37wcLNmA=="
+ },
+ "favicon/apple-touch-icon-144x144.png": {
+ "src": "favicon/apple-touch-icon-144x144.png",
+ "integrity": "sha512-RcXaoNQ/5TvDfRK3B16Xmbool22kaq9anaZ/+bxz6T4IkXly6Ss4V7E7sjAHY0z9VdBi8RlOXmCf1QVF/bO1UQ=="
+ },
+ "favicon/android-chrome-144x144.png": {
+ "src": "favicon/android-chrome-144x144.png",
+ "integrity": "sha512-MwJ9846H56kKjlblEn11IvX5wwgw8thJRda/Oz17yUs75jussMZX4XX5CFgp+Fgcj00FydeEm2x5QX4aay2H4w=="
+ },
+ "favicon/mstile-144x144.png": {
+ "src": "favicon/mstile-144x144.png",
+ "integrity": "sha512-MwJ9846H56kKjlblEn11IvX5wwgw8thJRda/Oz17yUs75jussMZX4XX5CFgp+Fgcj00FydeEm2x5QX4aay2H4w=="
+ },
+ "favicon/mstile-310x150.png": {
+ "src": "favicon/mstile-310x150.png",
+ "integrity": "sha512-533u9y8NEHRs6GP6+n7s7h296T50Y8dwB8FcS5htN7k+V9hWfurx6zfeqw6nDA9r9viOcKQXlJ/XfZLEpaMGMA=="
+ },
+ "favicon/mstile-150x150.png": {
+ "src": "favicon/mstile-150x150.png",
+ "integrity": "sha512-jm3Ncpm56VyOSvOsiKRMhX/AYl6vbZr9n80if2QsEyx/Rk9/+owriCEhlKkQ0krUrlEvvAh4Yy40JIiB7GHZYw=="
+ },
+ "favicon/apple-touch-icon-114x114.png": {
+ "src": "favicon/apple-touch-icon-114x114.png",
+ "integrity": "sha512-ZiGvyFWIDPl9YZ+NOn93b/7EpDtrw97agCizkuDdFRLr9I2u9FFZTnoik7LJapL3dnDGYD0E8qTJULOwMAthzA=="
+ },
+ "favicon/apple-touch-icon-120x120.png": {
+ "src": "favicon/apple-touch-icon-120x120.png",
+ "integrity": "sha512-0PVV+vO18IoVIOgedCOGdzRv6DF/71ygDGR7ijVJOT06xOsACnKooiS25YcXg6sVYjSBNO9omRGqYS+icunJCw=="
+ },
+ "favicon/manifest.webmanifest": {
+ "src": "favicon/manifest.webmanifest",
+ "integrity": "sha512-jWI8l1WzeZTVACRS28IeRRCxVue3FSmpky9ou90cG6sc7e9kmJtfQ9NfoFMYyOZ0xIqiA6N2FFD1e/Sx7VXK4g=="
+ },
+ "favicon/android-chrome-96x96.png": {
+ "src": "favicon/android-chrome-96x96.png",
+ "integrity": "sha512-Ml8MN6tFQcvVu1M9uFZyZxrtkJwcQv1i/VBs+6YDFvfNkGkvAMGmD3xmvS6qPbc6zazvpncQoAwihcwDYQ1DdQ=="
+ },
+ "mobile.scss": {
+ "src": "mobile-79ddc617.min.css",
+ "integrity": "sha512-dzw2wMOouDwhSgstQKLbXD/vIqS48Ttc2IV6DeG7yam9yvKUuChJVaworzL8s2UoGMX4x2jEm50PjFJE4R4QWw=="
+ },
+ "favicon/apple-touch-icon-72x72.png": {
+ "src": "favicon/apple-touch-icon-72x72.png",
+ "integrity": "sha512-xtDi3mPErMdQnOCAF36WY9+Yb9IEgFiWZxcwfI8ZyzLM+zSVXieiTNgvMp3Q7FKbYzuO/YbcY34aSpDeNbwSkw=="
+ },
+ "favicon/apple-touch-icon-76x76.png": {
+ "src": "favicon/apple-touch-icon-76x76.png",
+ "integrity": "sha512-5mXpJ0SOGLyJhM+1sKauzI78IZ2e3KX0Ut6bakTWECIS+GMtGU9i4YX2kCgIlf6MYD8NlHhNjgjTXguCQvpOkQ=="
+ },
+ "favicon/android-chrome-72x72.png": {
+ "src": "favicon/android-chrome-72x72.png",
+ "integrity": "sha512-yRiTvAL7S+LN+QqFT20OKvlUxy76dWOdYDt/oYrlvlITmWTB+IT3zscjYV3a+eQK0aaBnI3BYvyPpP0Jl0cp/w=="
+ },
+ "favicon/mstile-70x70.png": {
+ "src": "favicon/mstile-70x70.png",
+ "integrity": "sha512-YR17fb3y2Mop9r3sGULUWVS08KBsjl541ijD4NfjH9B7MHXot+bKNm+xtqlYSrTNnh1Q5swG1pE8ilH8aT77kA=="
+ },
+ "favicon/apple-touch-icon-57x57.png": {
+ "src": "favicon/apple-touch-icon-57x57.png",
+ "integrity": "sha512-3QaWN6DLuPtw8MP7aduHbuO1xiPEJlWE5WCckCnbLThBoYUOB1RV8flSAFAE11UpmqefMB4r2sWwuGRuHFSCtg=="
+ },
+ "favicon/apple-touch-icon-60x60.png": {
+ "src": "favicon/apple-touch-icon-60x60.png",
+ "integrity": "sha512-tHDTnMw35Ydrn4aUvkaXwVUsqBjboI2vqm3n2lL5jf21t6SMoekze+YFNC0MBNWEG08ajVQ9L7Qljf9Z2evhBA=="
+ },
+ "favicon/favicon-48x48.png": {
+ "src": "favicon/favicon-48x48.png",
+ "integrity": "sha512-Yp178+WA3ntd5AMrdskywuc8ubmWN9qqghWXAyyzbpBBMhKplIP2BveCOP6R16ZUGOcyzPnzjSRY3yESXjcZCQ=="
+ },
+ "favicon/android-chrome-48x48.png": {
+ "src": "favicon/android-chrome-48x48.png",
+ "integrity": "sha512-pPHYffX13GvEmTZMLvEocQDWE7rdp0KIM7cdY3w24+3H37j5vbo7K2xsCR92GpzBNXkw0hzcJcdyktaT+E1sag=="
+ },
+ "favicon/favicon-32x32.png": {
+ "src": "favicon/favicon-32x32.png",
+ "integrity": "sha512-5elFUf6p+aWoJI3WIS3dhk3MIAqMMM1XFsVZpzG63sITcr1I8iAfjsCIYTJ3fTvSSoFlFRKZ9djMVSNDEK6DqA=="
+ },
+ "favicon/android-chrome-36x36.png": {
+ "src": "favicon/android-chrome-36x36.png",
+ "integrity": "sha512-+cyRuV3w4FEq8DVZRGZ9CTiVja2RtOd9PmAIRciFDEpBX3KhdWS8sbLVl7FQ/yX5IkB8xmPla4VJjcgpcftO8w=="
+ },
+ "print.scss": {
+ "src": "print-72068949.min.css",
+ "integrity": "sha512-uuCwn+/RdwIo3i0FQEJpU2BX38diEpzBQD6eDEePbDmzjYTil/TI9ijRDEUGSqnXSL9pX+YPNzsQJDxPlBG92g=="
+ },
+ "favicon/favicon-16x16.png": {
+ "src": "favicon/favicon-16x16.png",
+ "integrity": "sha512-w2lU/rHj2Yf/yb5QMLW9CMSVv8jCr2kBqvqekSINDI7K7oga1RSeCPEtgcSy9n6zQzdFOmswybhPtNJhPcD9TA=="
+ },
+ "favicon/browserconfig.xml": {
+ "src": "favicon/browserconfig.xml",
+ "integrity": "sha512-cUHMy43WEDyWiiDTIcOab69HpATbZfoMFHJTYFx3SiU+vXLMHqo3w3mgQnrvdfs42gp37T+bw05l1qLFxlGwoA=="
+ },
+ "custom.css": {
+ "src": "custom.css",
+ "integrity": "sha512-1kALo+zc1L2u1rvyxPIew+ZDPWhnIA1Ei2rib3eHHbskQW+EMxfI9Ayyva4aV+YRrHvH0zFxvPSFIuZ3mfsbRA=="
+ }
+}
\ No newline at end of file
diff --git a/docs/themes/hugo-geekdoc/eslint.config.js b/docs/themes/hugo-geekdoc/eslint.config.js
new file mode 100644
index 00000000..42e87cbc
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/eslint.config.js
@@ -0,0 +1,22 @@
+import eslint from "@eslint/js";
+import globals from "globals";
+import babelParser from "@babel/eslint-parser";
+import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
+
+export default [
+ eslint.configs.recommended,
+ {
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ },
+ parser: babelParser,
+ ecmaVersion: 2022,
+ sourceType: "module",
+ parserOptions: {
+ requireConfigFile: false,
+ },
+ },
+ },
+ eslintPluginPrettierRecommended,
+];
diff --git a/docs/themes/hugo-geekdoc/i18n/am.yaml b/docs/themes/hugo-geekdoc/i18n/am.yaml
new file mode 100644
index 00000000..b9db5dc8
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/am.yaml
@@ -0,0 +1,52 @@
+---
+edit_page: ገጹን ማስተካከያ
+
+nav_navigation: መሄጃ
+nav_tags: መለያዎች
+nav_more: ተጨማሪ
+nav_top: ወደ ላይ ተመለስ
+
+form_placeholder_search: ፈልግ
+
+error_page_title: ጠፋብዎት? አይጨነቁ።
+error_message_title: ጠፋብዎት?
+error_message_code: አልተገኘም
+error_message_text: >
+ ገጹን ማግኘት አልተቻለም፤ ነገር ግን አይጨነቁ፤ በዚህ ገጽ መመለስ ይችላሉ።
+
+button_toggle_dark: ብሩህ/ጨለማ መቀያየሪያ
+button_nav_open: መሄጃውን ክፈት
+button_nav_close: መሄጃውን ዝጋ
+button_menu_open: ምርጫዎችን ክፈት
+button_menu_close: ምርጫዎችን ዝጋ
+button_homepage: ወደ መጀመሪያ ገጽ ተመለስ
+
+title_anchor_prefix: "ማያያዣ ወደ:"
+
+posts_read_more: ሙሉውን ያንብቡ
+posts_read_time:
+ one: "ለማንበብ አንድ ደቂቃ"
+ other: "{{ . }} ደቂቃዎች ለማንበብ"
+posts_update_prefix: መጨረሻ የዘመነው
+posts_count:
+ one: "አንድ ጽሑፍ"
+ other: "{{ . }} ጽሑፎች"
+posts_tagged_with: ከ '{{ . }}' ጋር የተዛመዱ ጽሑፎች በሙሉ
+
+footer_build_with: >
+ በ Hugo የተገነባ ከ
+ ጋር
+footer_legal_notice: ሕጋዊ መረጃዎች
+footer_privacy_policy: ስለ መረጃዎ አያያዝ ያለን አቋም
+footer_content_license_prefix: >
+ ስለ ይዘቱ ባለመብትነት መረጃ
+
+language_switch_no_tranlation_prefix: "ያልተተረጐመ ገጽ:"
+
+propertylist_required: ግድ የሚያስፈልግ
+propertylist_optional: ግድ ያልሆነ
+propertylist_default: በባዶ ፈንታ
+
+pagination_page_prev: ያለፈው
+pagination_page_next: ቀጣይ
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/cs.yaml b/docs/themes/hugo-geekdoc/i18n/cs.yaml
new file mode 100644
index 00000000..71dd8ed3
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/cs.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Upravit stránku
+
+nav_navigation: Navigace
+nav_tags: Tagy
+nav_more: Více
+nav_top: Zpět nahoru
+
+form_placeholder_search: Vyhledat
+
+error_page_title: Ztracen? Nic se neděje
+error_message_title: Ztracen?
+error_message_code: Error 404
+error_message_text: >
+ Vypadá to že stránka, kterou hledáte, neexistuje. Nemějte obavy, můžete
+ se vrátit zpět na domovskou stránku.
+
+button_toggle_dark: Přepnout tmavý/světlý/automatický režim
+button_nav_open: Otevřít navigaci
+button_nav_close: Zavřít navigaci
+button_menu_open: Otevřít lištu nabídky
+button_menu_close: Zavřít lištu nabídky
+button_homepage: Zpět na domovskou stránku
+
+title_anchor_prefix: "Odkaz na:"
+
+posts_read_more: Přečíst celý příspěvek
+posts_read_time:
+ one: "Doba čtení: 1 minuta"
+ other: "Doba čtení: {{ . }} minut(y)"
+posts_update_prefix: Naposledy upraveno
+posts_count:
+ one: "Jeden příspěvek"
+ other: "Příspěvků: {{ . }}"
+posts_tagged_with: Všechny příspěvky označeny '{{ . }}'
+
+footer_build_with: >
+ Vytvořeno za pomocí Hugo a
+
+footer_legal_notice: Právní upozornění
+footer_privacy_policy: Zásady ochrany soukromí
+footer_content_license_prefix: >
+ Obsah licencovaný pod
+
+language_switch_no_tranlation_prefix: "Stránka není přeložena:"
+
+propertylist_required: povinné
+propertylist_optional: volitené
+propertylist_default: výchozí
+
+pagination_page_prev: předchozí
+pagination_page_next: další
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/da.yaml b/docs/themes/hugo-geekdoc/i18n/da.yaml
new file mode 100644
index 00000000..2ba96eaf
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/da.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Rediger side
+
+nav_navigation: Navigation
+nav_tags: Tags
+nav_more: Mere
+nav_top: Til toppen
+
+form_placeholder_search: Søg
+
+error_page_title: Faret vild? Bare rolig
+error_message_title: Lost?
+error_message_code: Fejl 404
+error_message_text: >
+ Det du leder efter kan ikke findes. Bare rolig, du kan komme tilbage til
+ forsiden.
+
+button_toggle_dark: Skift Dark/Light/Auto mode
+button_nav_open: Åben navigation
+button_nav_close: Luk navigation
+button_menu_open: Åben menubar
+button_menu_close: Luk menubar
+button_homepage: Tilbage til forsiden
+
+title_anchor_prefix: "Link til:"
+
+posts_read_more: Læs fulde indlæg
+posts_read_time:
+ one: "Et minut at gennemlæse"
+ other: "{{ . }} minutter at gennemlæse"
+posts_update_prefix: Opdateret den
+posts_count:
+ one: "Et indlæg"
+ other: "{{ . }} indlæg"
+posts_tagged_with: Alle indslag tagget med '{{ . }}'
+
+footer_build_with: >
+ Bygget med Hugo og
+
+footer_legal_notice: Forretningsbetingelser
+footer_privacy_policy: Privatlivspolitik
+footer_content_license_prefix: >
+ Indhold licenseret under
+
+language_switch_no_tranlation_prefix: "Indlæg ikke oversat:"
+
+propertylist_required: påkrævet
+propertylist_optional: valgfri
+propertylist_default: udgangspunkt
+
+pagination_page_prev: forrige
+pagination_page_next: næste
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/de.yaml b/docs/themes/hugo-geekdoc/i18n/de.yaml
new file mode 100644
index 00000000..ae3dc99f
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/de.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Seite bearbeiten
+
+nav_navigation: Navigation
+nav_tags: Tags
+nav_more: Weitere
+nav_top: Nach oben
+
+form_placeholder_search: Suchen
+
+error_page_title: Verlaufen? Keine Sorge
+error_message_title: Verlaufen?
+error_message_code: Fehler 404
+error_message_text: >
+ Wir können die Seite nach der Du gesucht hast leider nicht finden. Keine Sorge,
+ wir bringen Dich zurück zur Startseite.
+
+button_toggle_dark: Wechsel zwischen Dunkel/Hell/Auto Modus
+button_nav_open: Navigation öffnen
+button_nav_close: Navigation schließen
+button_menu_open: Menüband öffnen
+button_menu_close: Menüband schließen
+button_homepage: Zurück zur Startseite
+
+title_anchor_prefix: "Link zu:"
+
+posts_read_more: Ganzen Artikel lesen
+posts_read_time:
+ one: "Eine Minute Lesedauer"
+ other: "{{ . }} Minuten Lesedauer"
+posts_update_prefix: Aktualisiert am
+posts_count:
+ one: "Ein Artikel"
+ other: "{{ . }} Artikel"
+posts_tagged_with: Alle Artikel mit dem Tag '{{ . }}'
+
+footer_build_with: >
+ Entwickelt mit Hugo und
+
+footer_legal_notice: Impressum
+footer_privacy_policy: Datenschutzerklärung
+footer_content_license_prefix: >
+ Inhalt lizensiert unter
+
+language_switch_no_tranlation_prefix: "Seite nicht übersetzt:"
+
+propertylist_required: erforderlich
+propertylist_optional: optional
+propertylist_default: Standardwert
+
+pagination_page_prev: vorher
+pagination_page_next: weiter
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/en.yaml b/docs/themes/hugo-geekdoc/i18n/en.yaml
new file mode 100644
index 00000000..ff19ea4e
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/en.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Edit page
+
+nav_navigation: Navigation
+nav_tags: Tags
+nav_more: More
+nav_top: Back to top
+
+form_placeholder_search: Search
+
+error_page_title: Lost? Don't worry
+error_message_title: Lost?
+error_message_code: Error 404
+error_message_text: >
+ Seems like what you are looking for can't be found. Don't worry, we can
+ bring you back to the homepage.
+
+button_toggle_dark: Toggle Dark/Light/Auto mode
+button_nav_open: Open Navigation
+button_nav_close: Close Navigation
+button_menu_open: Open Menu Bar
+button_menu_close: Close Menu Bar
+button_homepage: Back to homepage
+
+title_anchor_prefix: "Anchor to:"
+
+posts_read_more: Read full post
+posts_read_time:
+ one: "One minute to read"
+ other: "{{ . }} minutes to read"
+posts_update_prefix: Updated on
+posts_count:
+ one: "One post"
+ other: "{{ . }} posts"
+posts_tagged_with: All posts tagged with '{{ . }}'
+
+footer_build_with: >
+ Built with Hugo and
+
+footer_legal_notice: Legal Notice
+footer_privacy_policy: Privacy Policy
+footer_content_license_prefix: >
+ Content licensed under
+
+language_switch_no_tranlation_prefix: "Page not translated:"
+
+propertylist_required: required
+propertylist_optional: optional
+propertylist_default: default
+
+pagination_page_prev: prev
+pagination_page_next: next
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/es.yaml b/docs/themes/hugo-geekdoc/i18n/es.yaml
new file mode 100644
index 00000000..8e65cec7
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/es.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Editar página
+
+nav_navigation: Navegación
+nav_tags: Etiquetas
+nav_more: Más
+nav_top: Inicio de la página
+
+form_placeholder_search: Buscar
+
+error_page_title: Perdido? No te preocupes
+error_message_title: Perdido?
+error_message_code: Error 404
+error_message_text: >
+ Al parecer, lo que estás buscando no pudo ser encontrado. No te preocupes, podemos
+ llevarte de vuelta al inicio.
+
+button_toggle_dark: Cambiar el modo Oscuro/Claro/Auto
+button_nav_open: Abrir la Navegación
+button_nav_close: Cerrar la Navegación
+button_menu_open: Abrir el Menú Bar
+button_menu_close: Cerrar el Menú Bar
+button_homepage: Volver al Inicio
+
+title_anchor_prefix: "Anclado a:"
+
+posts_read_more: Lee la publicación completa
+posts_read_time:
+ one: "Un minuto para leer"
+ other: "{{ . }} minutos para leer"
+posts_update_prefix: Actualizado en
+posts_count:
+ one: "Una publicación"
+ other: "{{ . }} publicaciones"
+posts_tagged_with: Todas las publicaciones etiquetadas con '{{ . }}'
+
+footer_build_with: >
+ Creado con Hugo y
+
+footer_legal_notice: Aviso Legal
+footer_privacy_policy: Política de Privacidad
+footer_content_license_prefix: >
+ Contenido licenciado con
+
+language_switch_no_tranlation_prefix: "Página no traducida:"
+
+propertylist_required: requerido
+propertylist_optional: opcional
+propertylist_default: estándar
+
+pagination_page_prev: previo
+pagination_page_next: siguiente
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/fr.yaml b/docs/themes/hugo-geekdoc/i18n/fr.yaml
new file mode 100644
index 00000000..bbded857
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/fr.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Editer la page
+
+nav_navigation: Navigation
+nav_tags: Tags
+nav_more: Plus
+nav_top: Retour au haut de page
+
+form_placeholder_search: Chercher
+
+error_page_title: Perdu? Ne t'inquiète pas
+error_message_title: Perdu?
+error_message_code: Error 404
+error_message_text: >
+ On dirait que ce que vous cherchez est introuvable. Ne vous inquiétez pas, nous pouvons
+ vous ramèner à la page d'accueil.
+
+button_toggle_dark: Basculer le mode Sombre/Clair/Auto
+button_nav_open: Ouvrir la navigation
+button_nav_close: Fermer la navigation
+button_menu_open: Ouvrir la barre de menus
+button_menu_close: Fermer la barre de menus
+button_homepage: retour à la page d'accueil
+
+title_anchor_prefix: "Ancrer à :"
+
+posts_read_more: Lire l'article complet
+posts_read_time:
+ one: "Une minute pour lire"
+ other: "{{ . }} minutes à lire"
+posts_update_prefix: Mis à jour le
+posts_count:
+ one: "Un billet"
+ other: "{{ . }} billets"
+posts_tagged_with: Tous les articles marqués avec '{{ . }}'
+
+footer_build_with: >
+ Construit avec Hugo et
+
+footer_legal_notice: Mentions légales
+footer_privacy_policy: Politique de confidentialité
+footer_content_license_prefix: >
+ Contenu sous licence
+
+language_switch_no_tranlation_prefix: "Page non traduite:"
+
+propertylist_required: requis
+propertylist_optional: facultatif
+propertylist_default: défaut
+
+pagination_page_prev: précédent
+pagination_page_next: suivant
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/it.yaml b/docs/themes/hugo-geekdoc/i18n/it.yaml
new file mode 100644
index 00000000..ce7c40b4
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/it.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Modifica la pagina
+
+nav_navigation: Navigazione
+nav_tags: Etichette
+nav_more: Altro
+nav_top: Torna su
+
+form_placeholder_search: Cerca
+
+error_page_title: Perso? Non ti preoccupare
+error_message_title: Perso?
+error_message_code: Errore 404
+error_message_text: >
+ Sembra che non sia possibile trovare quello che stavi cercando. Non ti preoccupare,
+ possiamo riportarti alla pagina iniziale.
+
+button_toggle_dark: Seleziona il tema Chiaro/Scuro/Automatico
+button_nav_open: Apri la Navigazione
+button_nav_close: Chiudi la Navigazione
+button_menu_open: Apri la Barra del Menu
+button_menu_close: Chiudi la Barra del Menu
+button_homepage: Torna alla pagina iniziale
+
+title_anchor_prefix: "Ancora a:"
+
+posts_read_more: Leggi tutto il post
+posts_read_time:
+ one: "Tempo di lettura: un minuto"
+ other: "Tempo di lettura: {{ . }} minuti"
+posts_update_prefix: Aggiornato il
+posts_count:
+ one: "Un post"
+ other: "{{ . }} post"
+posts_tagged_with: Tutti i post etichettati con '{{ . }}'
+
+footer_build_with: >
+ Realizzato con Hugo e
+
+footer_legal_notice: Avviso Legale
+footer_privacy_policy: Politica sulla Privacy
+footer_content_license_prefix: >
+ Contenuto sotto licenza
+
+language_switch_no_tranlation_prefix: "Pagina non tradotta:"
+
+propertylist_required: richiesto
+propertylist_optional: opzionale
+propertylist_default: valore predefinito
+
+pagination_page_prev: precedente
+pagination_page_next: prossimo
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/ja.yaml b/docs/themes/hugo-geekdoc/i18n/ja.yaml
new file mode 100644
index 00000000..506e7b4e
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/ja.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: ページの編集
+
+nav_navigation: ナビゲーション
+nav_tags: タグ
+nav_more: さらに
+nav_top: トップへ戻る
+
+form_placeholder_search: 検索
+
+error_page_title: お困りですか?ご心配なく
+error_message_title: お困りですか?
+error_message_code: 404 エラー
+error_message_text: >
+ お探しのものが見つからないようです。トップページ
+ へ戻ることができるので、ご安心ください。
+
+button_toggle_dark: モードの切替 ダーク/ライト/自動
+button_nav_open: ナビゲーションを開く
+button_nav_close: ナビゲーションを閉じる
+button_menu_open: メニューバーを開く
+button_menu_close: メニューバーを閉じる
+button_homepage: トップページへ戻る
+
+title_anchor_prefix: "アンカー先:"
+
+posts_read_more: 全投稿を閲覧
+posts_read_time:
+ one: "読むのに 1 分かかります"
+ other: "読むのに要する時間 {{ . }} (分)"
+posts_update_prefix: 更新時刻
+posts_count:
+ one: "一件の投稿"
+ other: "{{ . }} 件の投稿"
+posts_tagged_with: "'{{ . }}'のタグが付いた記事全部"
+
+footer_build_with: >
+ Hugo でビルドしています。
+
+footer_legal_notice: 法的な告知事項
+footer_privacy_policy: プライバシーポリシー
+footer_content_license_prefix: >
+ 提供するコンテンツのライセンス
+
+language_switch_no_tranlation_prefix: "未翻訳のページ:"
+
+propertylist_required: 必須
+propertylist_optional: 任意
+propertylist_default: 既定値
+
+pagination_page_prev: 前
+pagination_page_next: 次
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/nl.yaml b/docs/themes/hugo-geekdoc/i18n/nl.yaml
new file mode 100644
index 00000000..8e24d62a
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/nl.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Wijzig pagina
+
+nav_navigation: Navigatie
+nav_tags: Markering
+nav_more: Meer
+nav_top: Terug naar boven
+
+form_placeholder_search: Zoek
+
+error_page_title: Verdwaald? Geen probleem
+error_message_title: Verdwaald?
+error_message_code: Error 404
+error_message_text: >
+ Het lijkt er op dat wat je zoekt niet gevonden kan worden. Geen probleem,
+ we kunnen je terug naar de startpagina brengen.
+
+button_toggle_dark: Wijzig Donker/Licht/Auto weergave
+button_nav_open: Open navigatie
+button_nav_close: Sluit navigatie
+button_menu_open: Open menubalk
+button_menu_close: Sluit menubalk
+button_homepage: Terug naar startpagina
+
+title_anchor_prefix: "Link naar:"
+
+posts_read_more: Lees volledige bericht
+posts_read_time:
+ one: "Een minuut leestijd"
+ other: "{{ . }} minuten leestijd"
+posts_update_prefix: Bijgewerkt op
+posts_count:
+ one: "Een bericht"
+ other: "{{ . }} berichten"
+posts_tagged_with: Alle berichten gemarkeerd met '{{ . }}'
+
+footer_build_with: >
+ Gebouwd met Hugo en
+
+footer_legal_notice: Juridische mededeling
+footer_privacy_policy: Privacybeleid
+footer_content_license_prefix: >
+ Inhoud gelicenseerd onder
+
+language_switch_no_tranlation_prefix: "Pagina niet vertaald:"
+
+propertylist_required: verplicht
+propertylist_optional: optioneel
+propertylist_default: standaard
+
+pagination_page_prev: vorige
+pagination_page_next: volgende
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/oc.yaml b/docs/themes/hugo-geekdoc/i18n/oc.yaml
new file mode 100644
index 00000000..a68685f3
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/oc.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: Modificar la pagina
+
+nav_navigation: Navegacion
+nav_tags: Etiquetas
+nav_more: Mai
+nav_top: Tornar ennaut
+
+form_placeholder_search: Cercar
+
+error_page_title: Perdut ? Cap de problèma
+error_message_title: Perdut ?
+error_message_code: Error 404
+error_message_text: >
+ Sembla que cercatz quicòm que se pòt pas trobat. Vos’n fagatz pas vos podèm
+ tornar a la pagina d’acuèlh.
+
+button_toggle_dark: Alternar lo mòde escur/clar/auto
+button_nav_open: Dobrir la navegacion
+button_nav_close: Tampar la navegacion
+button_menu_open: Dobrir la barra de menú
+button_menu_close: Tampar la barra de menú
+button_homepage: Tornar a la pagina d’acuèlh
+
+title_anchor_prefix: "Ancorar a:"
+
+posts_read_more: Legir la publicacion complèta
+posts_read_time:
+ one: "Una minuta de lectura"
+ other: "{{ . }} minutas de lectura"
+posts_update_prefix: Actualizada lo
+posts_count:
+ one: "Una publicacion"
+ other: "{{ . }} publicacions"
+posts_tagged_with: Totas las publicacions amb '{{ . }}'
+
+footer_build_with: >
+ Construch amb Hugo e
+
+footer_legal_notice: Mencions legalas
+footer_privacy_policy: politica de confidencialitat
+footer_content_license_prefix: >
+ Contengut sota licéncia
+
+language_switch_no_tranlation_prefix: "Pagina non traducha :"
+
+propertylist_required: requerit
+propertylist_optional: opcional
+propertylist_default: per defaut
+
+pagination_page_prev: prec.
+pagination_page_next: seg.
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/i18n/zh-cn.yaml b/docs/themes/hugo-geekdoc/i18n/zh-cn.yaml
new file mode 100644
index 00000000..e6403acd
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/i18n/zh-cn.yaml
@@ -0,0 +1,53 @@
+---
+edit_page: 编辑页面
+
+nav_navigation: 导航
+nav_tags: 标签
+nav_more: 更多
+nav_top: 回到顶部
+
+form_placeholder_search: 搜索
+
+error_page_title: 迷路了? 不用担心
+error_message_title: 迷路了?
+error_message_code: 错误 404
+error_message_text: >
+ 好像找不到你要找的东西。 别担心,我们可以
+ 带您回到主页。
+
+button_toggle_dark: 切换暗/亮/自动模式
+button_nav_open: 打开导航
+button_nav_close: 关闭导航
+button_menu_open: 打开菜单栏
+button_menu_close: 关闭菜单栏
+button_homepage: 返回首页
+
+title_anchor_prefix: "锚定到:"
+
+posts_read_more: 阅读全文
+posts_read_time:
+ one: "一分钟阅读时间"
+ other: "{{ . }} 分钟阅读时间"
+posts_update_prefix: 更新时间
+posts_count:
+ one: 一篇文章
+ other: "{{ . }} 个帖子"
+posts_tagged_with: 所有带有“{{ . }}”标签的帖子。
+
+footer_build_with: >
+ 基于 Hugo
+ 制作
+footer_legal_notice: "法律声明"
+footer_privacy_policy: "隐私政策"
+footer_content_license_prefix: >
+ 内容许可证
+
+language_switch_no_tranlation_prefix: "页面未翻译:"
+
+propertylist_required: 需要
+propertylist_optional: 可选
+propertylist_default: 默认值
+
+pagination_page_prev: 以前
+pagination_page_next: 下一个
+pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}"
diff --git a/docs/themes/hugo-geekdoc/images/readme.png b/docs/themes/hugo-geekdoc/images/readme.png
new file mode 100644
index 00000000..10c8ff15
Binary files /dev/null and b/docs/themes/hugo-geekdoc/images/readme.png differ
diff --git a/docs/themes/hugo-geekdoc/images/screenshot.png b/docs/themes/hugo-geekdoc/images/screenshot.png
new file mode 100644
index 00000000..af243606
Binary files /dev/null and b/docs/themes/hugo-geekdoc/images/screenshot.png differ
diff --git a/docs/themes/hugo-geekdoc/images/tn.png b/docs/themes/hugo-geekdoc/images/tn.png
new file mode 100644
index 00000000..ee6e42ed
Binary files /dev/null and b/docs/themes/hugo-geekdoc/images/tn.png differ
diff --git a/docs/themes/hugo-geekdoc/layouts/404.html b/docs/themes/hugo-geekdoc/layouts/404.html
new file mode 100644
index 00000000..ee7ba2d5
--- /dev/null
+++ b/docs/themes/hugo-geekdoc/layouts/404.html
@@ -0,0 +1,40 @@
+
+
+
+ {{ partial "head/meta" . }}
+ {{ i18n "error_page_title" }}
+
+ {{ partial "head/favicons" . }}
+ {{ partial "head/others" . }}
+
+
+
+ {{ partial "svg-icon-symbols" . }}
+
+
+