From ca39cc4269e73d14249a21a2fb8001ad05c22a55 Mon Sep 17 00:00:00 2001 From: Arcabot of Arcalot <110231824+arcalot-bot@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:44:10 +0000 Subject: [PATCH] Website publish --- 404.html | 6 +- arcaflow/concepts/architecture/index.html | 6 +- arcaflow/concepts/index.html | 6 +- arcaflow/concepts/plugins/index.html | 6 +- arcaflow/concepts/typing/index.html | 6 +- arcaflow/concepts/workflows/index.html | 91 +++-- arcaflow/contributing/deployers/index.html | 6 +- arcaflow/contributing/engine/index.html | 6 +- arcaflow/contributing/expressions/index.html | 6 +- arcaflow/contributing/index.html | 6 +- .../contributing/plugin-protocol/index.html | 6 +- arcaflow/contributing/typing/index.html | 6 +- arcaflow/getting-started/index.html | 6 +- arcaflow/index.html | 6 +- arcaflow/plugins/go/first/index.html | 6 +- arcaflow/plugins/go/index.html | 6 +- arcaflow/plugins/go/schema/index.html | 6 +- arcaflow/plugins/index.html | 6 +- arcaflow/plugins/packaging/index.html | 6 +- arcaflow/plugins/python/data-model/index.html | 6 +- arcaflow/plugins/python/embedding/index.html | 6 +- arcaflow/plugins/python/faq/index.html | 6 +- arcaflow/plugins/python/first/index.html | 6 +- arcaflow/plugins/python/index.html | 6 +- arcaflow/plugins/python/official/index.html | 6 +- arcaflow/plugins/python/schema/index.html | 6 +- arcaflow/plugins/python/testing/index.html | 6 +- arcaflow/running/index.html | 6 +- arcaflow/running/running/index.html | 6 +- arcaflow/running/setup/index.html | 6 +- arcaflow/workflows/expressions/index.html | 6 +- arcaflow/workflows/flow-control/index.html | 379 +++++++++++++++++- arcaflow/workflows/index.html | 6 +- arcaflow/workflows/input/index.html | 6 +- arcaflow/workflows/output/index.html | 6 +- arcaflow/workflows/schemas/index.html | 6 +- arcaflow/workflows/step/index.html | 6 +- arcaflow/workflows/versioning/index.html | 6 +- arcalog/index.html | 6 +- ...07f07601.min.js => search.6ce7567c.min.js} | 6 +- ....min.js.map => search.6ce7567c.min.js.map} | 4 +- assets/stylesheets/main.06209087.min.css | 1 + assets/stylesheets/main.06209087.min.css.map | 1 + assets/stylesheets/main.35f28582.min.css | 1 - assets/stylesheets/main.35f28582.min.css.map | 1 - index.html | 6 +- search/search_index.json | 2 +- sitemap.xml | 78 ++-- sitemap.xml.gz | Bin 427 -> 427 bytes 49 files changed, 590 insertions(+), 202 deletions(-) rename assets/javascripts/workers/{search.07f07601.min.js => search.6ce7567c.min.js} (94%) rename assets/javascripts/workers/{search.07f07601.min.js.map => search.6ce7567c.min.js.map} (78%) create mode 100644 assets/stylesheets/main.06209087.min.css create mode 100644 assets/stylesheets/main.06209087.min.css.map delete mode 100644 assets/stylesheets/main.35f28582.min.css delete mode 100644 assets/stylesheets/main.35f28582.min.css.map diff --git a/404.html b/404.html index 00a380e..a7a738c 100644 --- a/404.html +++ b/404.html @@ -12,7 +12,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -1586,7 +1586,7 @@
The data type of the input on Step 2 in this case must match the result of the expression. If the data type does not match, the workflow will not be executed.
This is typically the case when credentials, such as database access, etc. are required.
The output for each step is preserved for later inspection. However, the workflow can explicitly declare outputs. These outputs are usable in scripted environments as a direct output of the workflow:
stateDiagram-v2
[*] --> Step
- Step --> [*]: output_1 = $.steps.step1.outputs.success
+ Step --> [*]: output_1 = !expr $.steps.step1.outputs.success
Background processes
-Each plugin will only be invoked once, allowing plugins to run background processes, such as server applications. The plugins must handle SIGINT and SIGTERM events properly.
+To allow background processes idiomatically, plugins can implement the cancellation signal to be stopped by the workflow.
The workflow contains several flow control operations. These flow control operations are not implemented by plugins, but are part of the workflow engine itself.
@@ -1805,11 +1805,41 @@This feature can be configured for parallel or sequential execution.
+A condition is a flow control operation that controls whether or not a step can run. You can also create multiple branches with opposing logic to create a switch-case effect.
+stateDiagram-v2
+ classDef tag font-style:italic;
+ classDef step font-weight:bold;
+
+ input
+ [*] --> Step1:::step
+ input --> Step1: !expr $.input.enabled
+ or_disabled:::tag: !ordisabled
+ Step1 --> or_disabled: success
+ Step1 --> or_disabled: Disabled
+ or_disabled --> [*]
+stateDiagram-v2
+ classDef tag font-style:italic;
+ classDef step font-weight:bold;
+
+ state if_state <<choice>>
+ Step1:::step: Step 1
+ [*] --> Step1
+ Step1 --> if_state
+ Step2:::step: Step 2
+ Step3:::step: Step 3
+ if_state --> Step2: !expr $.step1.output_1 == true
+ if_state --> Step3: !expr $.step1.output_1 == false
+ oneof:::tag: !oneof
+ Step2 --> oneof
+ Step3 --> oneof
+ oneof --> [*]
+Warning
-The features below are in-development and not yet implemented in the released codebase.
+This feature is not yet implemented.
The abort flow control is a quick way to exit out of a workflow. This is useful when entering a terminal error state and the workflow output data would be useless anyway.
stateDiagram-v2
[*] --> Step1
@@ -1818,12 +1848,16 @@ Abort
Step2 --> [*]
However, this is only required if you want to abort the workflow immediately. If you want an error case to result in the workflow failing, but whatever steps can be finished being finished, you can leave error outputs unconnected.
Warning
+This feature is not yet implemented.
+A do-while block will execute the steps in it as long as a certain condition is met. The condition is derived from the output of the step or steps executed inside the loop:
stateDiagram-v2
[*] --> DoWhile
state DoWhile {
[*] --> Step1
- Step1 --> [*]: output_1_condition=$.step1.output_1.finished == false
+ Step1 --> [*]: output_1_condition= !expr $.step1.output_1.finished == false
}
DoWhile --> [*]
If the step declares multiple outputs, multiple conditions are possible. The do-while block will also have multiple outputs:
@@ -1832,7 +1866,7 @@A condition is a flow control operation that redirects the flow one way or another based on an expression. You can also create multiple branches to create a switch-case effect.
-stateDiagram-v2
- state if_state <<choice>>
- Step1: Step 1
- [*] --> Step1
- Step1 --> if_state
- Step2: Step 2
- Step3: Step 3
- if_state --> Step2: $.step1.output_1 == true
- if_state --> Step3: $.step1.output_1 == false
Warning
+This feature is not yet implemented.
+The multiply flow control operation is useful when you need to dynamically execute sub-workflows in parallel based on an input condition. You can, for example, use this to run a workflow step on multiple or all Kubernetes nodes.
stateDiagram-v2
Lookup: Lookup Kubernetes hosts
@@ -1870,9 +1897,13 @@ Multiply
The output of a Multiply operation will be a map, keyed with a string that is configured from the input.
Tip
-You can think of a Multiply step like a for-each loop, but the steps being executed in parallel.
+You can think of a Multiply step like a variation of a for-each loop geared towards specific parallelization cases.
Synchronize
+
+Warning
+This feature is not yet implemented.
+
The synchronize step attempts to synchronize the execution of subsequent steps for a specified key. The key must be a constant and cannot be obtained from an input expression.
stateDiagram-v2
[*] --> Step1
@@ -1946,7 +1977,7 @@ Synchronize
-
+
diff --git a/arcaflow/contributing/deployers/index.html b/arcaflow/contributing/deployers/index.html
index 7bffa64..698225f 100644
--- a/arcaflow/contributing/deployers/index.html
+++ b/arcaflow/contributing/deployers/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1637,7 +1637,7 @@ Arcaflow Deployers Development Gui
-
+
diff --git a/arcaflow/contributing/engine/index.html b/arcaflow/contributing/engine/index.html
index 894aa8c..0aa4535 100644
--- a/arcaflow/contributing/engine/index.html
+++ b/arcaflow/contributing/engine/index.html
@@ -16,7 +16,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
@@ -1737,7 +1737,7 @@ Workflow execution
-
+
diff --git a/arcaflow/contributing/expressions/index.html b/arcaflow/contributing/expressions/index.html
index 0cf0208..2c4968a 100644
--- a/arcaflow/contributing/expressions/index.html
+++ b/arcaflow/contributing/expressions/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1762,7 +1762,7 @@ The API Layer
-
+
diff --git a/arcaflow/contributing/index.html b/arcaflow/contributing/index.html
index 1340ca6..0d3dfec 100644
--- a/arcaflow/contributing/index.html
+++ b/arcaflow/contributing/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1696,7 +1696,7 @@ The principle of kindness
-
+
diff --git a/arcaflow/contributing/plugin-protocol/index.html b/arcaflow/contributing/plugin-protocol/index.html
index 2077f21..7da215e 100644
--- a/arcaflow/contributing/plugin-protocol/index.html
+++ b/arcaflow/contributing/plugin-protocol/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -4454,7 +4454,7 @@ Schema
-
+
diff --git a/arcaflow/contributing/typing/index.html b/arcaflow/contributing/typing/index.html
index 697d929..0e2e7b1 100644
--- a/arcaflow/contributing/typing/index.html
+++ b/arcaflow/contributing/typing/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -4159,7 +4159,7 @@ Reference Manual
-
+
diff --git a/arcaflow/getting-started/index.html b/arcaflow/getting-started/index.html
index c5c3c36..849f789 100644
--- a/arcaflow/getting-started/index.html
+++ b/arcaflow/getting-started/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -2421,7 +2421,7 @@ Keep learning
-
+
diff --git a/arcaflow/index.html b/arcaflow/index.html
index 28b11c1..9c60c21 100644
--- a/arcaflow/index.html
+++ b/arcaflow/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1774,7 +1774,7 @@ Alternatives
-
+
diff --git a/arcaflow/plugins/go/first/index.html b/arcaflow/plugins/go/first/index.html
index b8892b3..05d1c12 100644
--- a/arcaflow/plugins/go/first/index.html
+++ b/arcaflow/plugins/go/first/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1859,7 +1859,7 @@ Next steps
-
+
diff --git a/arcaflow/plugins/go/index.html b/arcaflow/plugins/go/index.html
index 95dc5fa..35e9f2f 100644
--- a/arcaflow/plugins/go/index.html
+++ b/arcaflow/plugins/go/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1634,7 +1634,7 @@ Creating plugins in Go
-
+
diff --git a/arcaflow/plugins/go/schema/index.html b/arcaflow/plugins/go/schema/index.html
index f3a7ad7..44ddb10 100644
--- a/arcaflow/plugins/go/schema/index.html
+++ b/arcaflow/plugins/go/schema/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1995,7 +1995,7 @@ Units
-
+
diff --git a/arcaflow/plugins/index.html b/arcaflow/plugins/index.html
index 3d17941..30ea319 100644
--- a/arcaflow/plugins/index.html
+++ b/arcaflow/plugins/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1638,7 +1638,7 @@ Packaging plugins
-
+
diff --git a/arcaflow/plugins/packaging/index.html b/arcaflow/plugins/packaging/index.html
index fbf827a..5a2e93e 100644
--- a/arcaflow/plugins/packaging/index.html
+++ b/arcaflow/plugins/packaging/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1724,7 +1724,7 @@ The manual method
-
+
diff --git a/arcaflow/plugins/python/data-model/index.html b/arcaflow/plugins/python/data-model/index.html
index d92bb80..37f1223 100644
--- a/arcaflow/plugins/python/data-model/index.html
+++ b/arcaflow/plugins/python/data-model/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -2077,7 +2077,7 @@ Built-In Units
-
+
diff --git a/arcaflow/plugins/python/embedding/index.html b/arcaflow/plugins/python/embedding/index.html
index ae0ba26..924d0e5 100644
--- a/arcaflow/plugins/python/embedding/index.html
+++ b/arcaflow/plugins/python/embedding/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1667,7 +1667,7 @@ Embedding your Python plugin
-
+
diff --git a/arcaflow/plugins/python/faq/index.html b/arcaflow/plugins/python/faq/index.html
index 177c402..42f854b 100644
--- a/arcaflow/plugins/python/faq/index.html
+++ b/arcaflow/plugins/python/faq/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1720,7 +1720,7 @@ {"base": "../../../..", "features": ["navigation.instant", "navigation.tracking", "navigation.tabs", "navigation.tabs.sticky", "navigation.indexes", "toc.integrate", "search.suggest", "search.highlight", "search.share", "content.code.copy"], "search": "../../../../assets/javascripts/workers/search.07f07601.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}
+
diff --git a/arcaflow/plugins/python/first/index.html b/arcaflow/plugins/python/first/index.html
index 77574f4..1486f19 100644
--- a/arcaflow/plugins/python/first/index.html
+++ b/arcaflow/plugins/python/first/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1906,7 +1906,7 @@ Next steps
-
+
diff --git a/arcaflow/plugins/python/index.html b/arcaflow/plugins/python/index.html
index 51ccb53..85c3dac 100644
--- a/arcaflow/plugins/python/index.html
+++ b/arcaflow/plugins/python/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1633,7 +1633,7 @@ Creating plugins with Python
-
+
diff --git a/arcaflow/plugins/python/official/index.html b/arcaflow/plugins/python/official/index.html
index ecf071a..17cf238 100644
--- a/arcaflow/plugins/python/official/index.html
+++ b/arcaflow/plugins/python/official/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1816,7 +1816,7 @@ Publishing on PyPI
-
+
diff --git a/arcaflow/plugins/python/schema/index.html b/arcaflow/plugins/python/schema/index.html
index 1eee169..8a3ec10 100644
--- a/arcaflow/plugins/python/schema/index.html
+++ b/arcaflow/plugins/python/schema/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -2024,7 +2024,7 @@ Running the plugin
-
+
diff --git a/arcaflow/plugins/python/testing/index.html b/arcaflow/plugins/python/testing/index.html
index 16a94ec..757979f 100644
--- a/arcaflow/plugins/python/testing/index.html
+++ b/arcaflow/plugins/python/testing/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1781,7 +1781,7 @@ Functional tests
-
+
diff --git a/arcaflow/running/index.html b/arcaflow/running/index.html
index 5e02428..f96f883 100644
--- a/arcaflow/running/index.html
+++ b/arcaflow/running/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1631,7 +1631,7 @@ Running Arcaflow
-
+
diff --git a/arcaflow/running/running/index.html b/arcaflow/running/running/index.html
index 56d6b02..ef6ba00 100644
--- a/arcaflow/running/running/index.html
+++ b/arcaflow/running/running/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1730,7 +1730,7 @@ Execution
-
+
diff --git a/arcaflow/running/setup/index.html b/arcaflow/running/setup/index.html
index 986369b..a104b67 100644
--- a/arcaflow/running/setup/index.html
+++ b/arcaflow/running/setup/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -9719,7 +9719,7 @@ Step logging
-
+
diff --git a/arcaflow/workflows/expressions/index.html b/arcaflow/workflows/expressions/index.html
index ae6efc8..1a0cbec 100644
--- a/arcaflow/workflows/expressions/index.html
+++ b/arcaflow/workflows/expressions/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -2743,7 +2743,7 @@ Binding Constants
-
+
diff --git a/arcaflow/workflows/flow-control/index.html b/arcaflow/workflows/flow-control/index.html
index 41f342d..4cc88b6 100644
--- a/arcaflow/workflows/flow-control/index.html
+++ b/arcaflow/workflows/flow-control/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1356,6 +1356,96 @@
+ -
+
+
+ Conditional Step Execution
+
+
+
+
+
+
+
-
@@ -1652,6 +1742,273 @@
Using Flow Control Mechanics
Flow control allows the workflow author to build a workflow with a decision tree based on supported flow logic. These flow control operations are not implemented by plugins, but are part of the workflow engine itself.
+Conditional Step Execution
+Conditional step execution can be achieved with the enabled
input.
+The value passed into the enabled
field should be an expression that resolves as a boolean. So it should either reference a path to a boolean value, or it should be a binary comparison.
+A situation that would benefit from conditional step execution includes a step that uploads the result to a remote server. For development runs you may not want to upload the results, so you can create a bool
field in the input to toggle the upload, then you can reference that field in the enabled
field of the upload step.
+Example:
+
workflow.yamlversion: v0.2.0
+input:
+ root: RootObject
+ objects:
+ RootObject:
+ id: RootObject
+ properties:
+ step_1_enabled:
+ type:
+ type_id: bool
+ step_1_input:
+ type:
+ type_id: integer
+steps:
+ my_step:
+ plugin:
+ deployment_type: image
+ src: path/to/my_plugin:1
+ input:
+ param_1: !expr $.input.step_1_input
+ enabled: !expr $.input.step_1_enabled
+outputs:
+ workflow_success:
+ plugin_output: !expr $.steps.my_step.outputs.success
+
+But oh no! The workflow fails with the error all outputs marked as unresolvable
when the step is disabled.
+This happens because the workflow instructed the step not to run, and therefore the output workflow_success
+cannot get the information it needs. See the next section for methods of handling this situation.
+Graceful handling of disabled steps
+When steps are disabled, they no longer emit their step output. To handle this, the workflow output (or a
+following step) must have an OR dependency on both the step’s disable output and on another step that has
+opposite enable logic.
+An OR dependency means that the step will resolve when any of the nodes it depends on resolve, unlike the
+default AND logic that waits for all dependencies to resolve.
+Here is a visual demonstrating two steps that have opposite logic:
+
stateDiagram-v2
+ classDef tag font-style:italic;
+ classDef step font-weight:bold;
+
+ state if_state <<choice>>
+ Step1:::step: Step 1
+ [*] --> Step1
+ Step1 --> if_state
+ Step2:::step: Step 2
+ Step3:::step: Step 3
+ if_state --> Step2: !expr $.step1.output_1 == true
+ if_state --> Step3: !expr $.step1.output_1 == false
+ oneof:::tag: !oneof
+ Step2 --> oneof
+ Step3 --> oneof
+ oneof --> [*]
+Here is a visual demonstrating utilizing the disabled output:
+
stateDiagram-v2
+ classDef tag font-style:italic;
+ classDef step font-weight:bold;
+
+ input
+ [*] --> Step1:::step
+ input --> Step1: !expr $.input.enabled
+ or_disabled:::tag: !ordisabled
+ Step1 --> or_disabled: success
+ Step1 --> or_disabled: Disabled
+ or_disabled --> [*]
+The way this works is that either of the two paths can create a valid output, allowing the workflow to continue past
+the disabled steps.
+How to do this in a workflow
+There are two tags that create the described OR dependency.
+
+
+
+Tag
+Description
+
+
+
+
+!oneof
+A general use tag to depend on any of the inputs
+
+
+!ordisabled
+A special case of !oneof
that automatically handles the disabled case
+
+
+
+How to use !oneof
+To use !oneof
for graceful handling of disabled steps, the oneof should depend on ONE OF the success output and
+the disabled output, which will output either the success output or the disabled output.
+The oneof tag is a method of creating a schema one_of_string
type from values present in the workflow.
+The syntax of !oneof
is:
+- Following the tag !oneof
, create a new YAML section (map) by starting an indented new line. That section should contain two properties:
+ - discriminator: A string that specifies what the oneof discriminator should be. The discriminator specifies which option was emitted.
+ - options: A YAML section (map) that contains all options. The keys are the discriminator values, and the values should be valid expressions.
+Example:
+
workflow.yamlversion: v0.2.0
+input:
+ root: RootObject
+ objects:
+ RootObject:
+ id: RootObject
+ properties:
+ step_1_enabled:
+ type:
+ type_id: bool
+ step_1_input:
+ type:
+ type_id: integer
+steps:
+ my_step:
+ plugin:
+ deployment_type: image
+ src: path/to/my_plugin:1
+ input:
+ param_1: !expr $.input.step_1_input
+ enabled: !expr $.input.step_1_enabled
+outputs:
+ workflow_success:
+ plugin_output: !oneof
+ discriminator: result
+ options:
+ enabled: !expr $.steps.my_step.outputs.success
+ disabled: !expr $.steps.my_step.disabled.output
+
+How to use !ordisabled
+Arcaflow provides workflow developers an easy way to handle disabled steps that is as simple as changing the
+expression tag from !expr
to !ordisabled
.
+This is a special case of !oneof
with the discriminator set to result
, and the options set to enabled
and
+disabled
. The enabled output is the expression provided, and the disabled output is automatically generated from
+the expression provided.
+The input must be in one of the following formats:
+- $.steps.step_name.outputs
+- steps.step_name.outputs
+- $.steps.step_name.outputs.output_name
+- steps.step_name.outputs.output_name
+- $.steps.step_name.outputs.output_name.output_field
+- steps.step_name.outputs.output_name.output_field
+Using !ordisabled
, the following example is otherwise equivalent to the prior example:
+
workflow.yamlversion: v0.2.0
+input:
+ root: RootObject
+ objects:
+ RootObject:
+ id: RootObject
+ properties:
+ step_1_enabled:
+ type:
+ type_id: bool
+ step_1_input:
+ type:
+ type_id: integer
+steps:
+ my_step:
+ plugin:
+ deployment_type: image
+ src: path/to/my_plugin:1
+ input:
+ param_1: !expr $.input.step_1_input
+ enabled: !expr $.input.step_1_enabled
+outputs:
+ workflow_success:
+ plugin_output: !ordisabled $.steps.my_step.outputs.success
+
+Alternative methods
+For handling disabled steps, !oneof
and !ordisabled
are the recommended methods because they cause output failure
+when the step fails. However, if it is acceptable for the workflow to succeed when a step crashes, the optional tags
+could be the right feature for your workflow.
+The alterative methods are the “optional” family of expression tags. The oneof
tags instructed the workflow to
+build a oneof_string
type with OR dependencies, requiring one of the options to have an output for the oneof to
+resolve. Meanwhile, the “optional” family of tags can resolve without an output by utilizing optional object property
+fields.
+
+
+
+Tag
+Description
+
+
+
+
+!wait-optional
+The typical one that should be used. It will resolve either when the value becomes present, or when it is known that the value will not resolve.
+
+
+!soft-optional
+Not recommended for most use cases. It will not wait for the value to be ready. If the value is present, it will set the field. If it isn’t, it will exclude it.
+
+
+
+How to use !wait-optional
+workflow.yamlversion: v0.2.0
+input:
+ root: RootObject
+ objects:
+ RootObject:
+ id: RootObject
+ properties:
+ step_1_enabled:
+ type:
+ type_id: bool
+ step_1_input:
+ type:
+ type_id: integer
+steps:
+ my_step:
+ plugin:
+ deployment_type: image
+ src: path/to/my_plugin:1
+ input:
+ param_1: !expr $.input.step_1_input
+ enabled: !expr $.input.step_1_enabled
+outputs:
+ workflow_success:
+ plugin_output: !wait-optional $.steps.my_step.outputs.success
+
+If my_step
is enabled and has an output, it will be present in the plugin_output
field of the workflow_success
+output. Otherwise, the workflow_success
output will not include the plugin_output
field. The output will wait
+for the plugin to finish, if enabled.
+How to use !soft-optional
+
+Warning
+This feature is not recommended for most use cases.
+
+The !soft-optional
tag creates the loosest type of dependency. The output will not wait for the step to finish
+or fail. In the event that the step referenced with the !soft-optional
tag does not finish by the time the other
+dependencies resolve, or if it crashes or is disabled, the field will be left out of the output.
+The example used in !wait-optional
would not work with !soft-optional
because the output would immediately
+resolve without the plugin_output
field. A second dependency is required to ensure the output does not resolve immediately.
+workflow.yamlversion: v0.2.0
+input:
+ root: RootObject
+ objects:
+ RootObject:
+ id: RootObject
+ properties:
+ background_step_enabled:
+ type:
+ type_id: bool
+ background_step_input:
+ type:
+ type_id: integer
+ step_1_input:
+ type:
+ type_id: integer
+steps:
+ background_step:
+ plugin:
+ deployment_type: image
+ src: path/to/my_background_plugin:1
+ input:
+ param_1: !expr $.input.background_step_input
+ enabled: !expr $.input.background_step_enabled
+ my_step:
+ plugin:
+ deployment_type: image
+ src: path/to/my_plugin:1
+ input:
+ param_1: !expr $.input.step_1_input
+outputs:
+ workflow_success:
+ background_output: !soft-optional $.steps.background_step.outputs.success
+ plugin_output: $.steps.my_step.outputs.success
+
Foreach Loops
Foreach loops allow for running a sub-workflow with iterative inputs from a parent workflow. A sub-workflow is a complete Arcaflow workflow file with its own input and output schemas as described in this section. The inputs for the sub-workflow are provided as a list, where each list item is an object that matches the sub-workflow input schema.
@@ -1728,13 +2085,13 @@ Foreach Loops
type_id: string
steps:
my_plugin:
- plugin:
+ plugin:
deployment_type: image
src: path/to/my_plugin:1
input:
param_1: !expr $.input.param_1
my_other_plugin:
- plugin:
+ plugin:
deployment_type: image
src: path/to/my_other_plugin:1
input:
@@ -1746,11 +2103,11 @@ Foreach Loops
my_other_plugin: !expr $.steps.my_other_plugin.outputs.success
Reduce Repetition with bindConstants()
-The builtin function bindConstants()
allows you to
-avoid repeating input variables for a foreach
subworkflow. In the example
-below, the input variable name
’s value is repeated across each iteration in
-this input. This results in a more repetitive input and schema definition. This
-section will show you how to simplify it.
+The builtin function bindConstants()
allows you to
+avoid repeating input variables for a foreach
subworkflow. In the example
+below, the input variable name
’s value is repeated across each iteration in
+this input. This results in a more repetitive input and schema definition. This
+section will show you how to simplify it.
Workflow and Input Before bindConstants()
input-repeated.yamliterations:
- loop_id: 1
@@ -1818,7 +2175,7 @@ Workflow and Input Before type_id: string
ratio:
type:
- type_id: float
+ type_id: float
steps:
example:
@@ -1929,7 +2286,7 @@ Reduced Repetition Workflow
-
+
diff --git a/arcaflow/workflows/index.html b/arcaflow/workflows/index.html
index 48ba363..97a6927 100644
--- a/arcaflow/workflows/index.html
+++ b/arcaflow/workflows/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1644,7 +1644,7 @@ Schema Names
-
+
diff --git a/arcaflow/workflows/input/index.html b/arcaflow/workflows/input/index.html
index 8f3a115..af9fa6f 100644
--- a/arcaflow/workflows/input/index.html
+++ b/arcaflow/workflows/input/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -2109,7 +2109,7 @@ Units
-
+
diff --git a/arcaflow/workflows/output/index.html b/arcaflow/workflows/output/index.html
index b910d16..e77def0 100644
--- a/arcaflow/workflows/output/index.html
+++ b/arcaflow/workflows/output/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1701,7 +1701,7 @@ Writing multiple outputs
-
+
diff --git a/arcaflow/workflows/schemas/index.html b/arcaflow/workflows/schemas/index.html
index b9155fb..5615334 100644
--- a/arcaflow/workflows/schemas/index.html
+++ b/arcaflow/workflows/schemas/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1898,7 +1898,7 @@ Generated Combined Schema Names
-
+
diff --git a/arcaflow/workflows/step/index.html b/arcaflow/workflows/step/index.html
index 6ab4835..d849a32 100644
--- a/arcaflow/workflows/step/index.html
+++ b/arcaflow/workflows/step/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -9705,7 +9705,7 @@ Deployers
-
+
diff --git a/arcaflow/workflows/versioning/index.html b/arcaflow/workflows/versioning/index.html
index a74a498..8bef938 100644
--- a/arcaflow/workflows/versioning/index.html
+++ b/arcaflow/workflows/versioning/index.html
@@ -18,7 +18,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
@@ -1763,7 +1763,7 @@ 0.2.0
-
+
diff --git a/arcalog/index.html b/arcalog/index.html
index c7722ef..6181456 100644
--- a/arcalog/index.html
+++ b/arcalog/index.html
@@ -14,7 +14,7 @@
-
+
@@ -22,7 +22,7 @@
-
+
@@ -1614,7 +1614,7 @@ Arcalog: Assisted Ro
-
+
diff --git a/assets/javascripts/workers/search.07f07601.min.js b/assets/javascripts/workers/search.6ce7567c.min.js
similarity index 94%
rename from assets/javascripts/workers/search.07f07601.min.js
rename to assets/javascripts/workers/search.6ce7567c.min.js
index f3dbf56..8e7e550 100644
--- a/assets/javascripts/workers/search.07f07601.min.js
+++ b/assets/javascripts/workers/search.6ce7567c.min.js
@@ -1,4 +1,4 @@
-"use strict";(()=>{var xe=Object.create;var U=Object.defineProperty,ve=Object.defineProperties,Se=Object.getOwnPropertyDescriptor,Te=Object.getOwnPropertyDescriptors,Qe=Object.getOwnPropertyNames,Y=Object.getOwnPropertySymbols,Ee=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty,be=Object.prototype.propertyIsEnumerable;var Z=Math.pow,J=(t,e,r)=>e in t?U(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,A=(t,e)=>{for(var r in e||(e={}))X.call(e,r)&&J(t,r,e[r]);if(Y)for(var r of Y(e))be.call(e,r)&&J(t,r,e[r]);return t},G=(t,e)=>ve(t,Te(e));var Le=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var we=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Qe(e))!X.call(t,i)&&i!==r&&U(t,i,{get:()=>e[i],enumerable:!(n=Se(e,i))||n.enumerable});return t};var Pe=(t,e,r)=>(r=t!=null?xe(Ee(t)):{},we(e||!t||!t.__esModule?U(r,"default",{value:t,enumerable:!0}):r,t));var B=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var te=Le((K,ee)=>{/**
+"use strict";(()=>{var xe=Object.create;var U=Object.defineProperty,ve=Object.defineProperties,Se=Object.getOwnPropertyDescriptor,Te=Object.getOwnPropertyDescriptors,Qe=Object.getOwnPropertyNames,J=Object.getOwnPropertySymbols,Ee=Object.getPrototypeOf,Z=Object.prototype.hasOwnProperty,be=Object.prototype.propertyIsEnumerable;var K=Math.pow,X=(t,e,r)=>e in t?U(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,A=(t,e)=>{for(var r in e||(e={}))Z.call(e,r)&&X(t,r,e[r]);if(J)for(var r of J(e))be.call(e,r)&&X(t,r,e[r]);return t},G=(t,e)=>ve(t,Te(e));var Le=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var we=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Qe(e))!Z.call(t,i)&&i!==r&&U(t,i,{get:()=>e[i],enumerable:!(n=Se(e,i))||n.enumerable});return t};var Pe=(t,e,r)=>(r=t!=null?xe(Ee(t)):{},we(e||!t||!t.__esModule?U(r,"default",{value:t,enumerable:!0}):r,t));var B=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var re=Le((ee,te)=>{/**
* lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9
* Copyright (C) 2020 Oliver Nightingale
* @license MIT
@@ -37,6 +37,6 @@
*/t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof K=="object"?ee.exports=r():e.lunr=r()}(this,function(){return t})})()});var de=Pe(te());function re(t,e=document){let r=ke(t,e);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${t}" to be present`);return r}function ke(t,e=document){return e.querySelector(t)||void 0}Object.entries||(Object.entries=function(t){let e=[];for(let r of Object.keys(t))e.push([r,t[r]]);return e});Object.values||(Object.values=function(t){let e=[];for(let r of Object.keys(t))e.push(t[r]);return e});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(t,e){typeof t=="object"?(this.scrollLeft=t.left,this.scrollTop=t.top):(this.scrollLeft=t,this.scrollTop=e)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...t){let e=this.parentNode;if(e){t.length===0&&e.removeChild(this);for(let r=t.length-1;r>=0;r--){let n=t[r];typeof n=="string"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?e.insertBefore(this.previousSibling,n):e.replaceChild(n,this)}}}));function ne(t){let e=new Map;for(let r of t){let[n]=r.location.split("#"),i=e.get(n);typeof i=="undefined"?e.set(n,r):(e.set(r.location,r),r.parent=i)}return e}function W(t,e,r){var s;e=new RegExp(e,"g");let n,i=0;do{n=e.exec(t);let o=(s=n==null?void 0:n.index)!=null?s:t.length;if(in?e(r,1,n,n=i):t.charAt(i)===">"&&(t.charAt(n+1)==="/"?--s===0&&e(r++,2,n,i+1):t.charAt(i-1)!=="/"&&s++===0&&e(r,0,n,i+1),n=i+1);i>n&&e(r,1,n,i)}function se(t,e,r,n=!1){return q([t],e,r,n).pop()}function q(t,e,r,n=!1){let i=[0];for(let s=1;s>>2&1023,c=a[0]>>>12;i.push(+(u>c)+i[i.length-1])}return t.map((s,o)=>{let a=0,u=new Map;for(let f of r.sort((g,l)=>g-l)){let g=f&1048575,l=f>>>20;if(i[l]!==o)continue;let m=u.get(l);typeof m=="undefined"&&u.set(l,m=[]),m.push(g)}if(u.size===0)return s;let c=[];for(let[f,g]of u){let l=e[f],m=l[0]>>>12,x=l[l.length-1]>>>12,v=l[l.length-1]>>>2&1023;n&&m>a&&c.push(s.slice(a,m));let d=s.slice(m,x+v);for(let y of g.sort((b,E)=>E-b)){let b=(l[y]>>>12)-m,E=(l[y]>>>2&1023)+b;d=[d.slice(0,b),"",d.slice(b,E),"",d.slice(E)].join("")}if(a=x+v,c.push(d)===2)break}return n&&a{var f;switch(i[f=o+=s]||(i[f]=[]),a){case 0:case 2:i[o].push(u<<12|c-u<<2|a);break;case 1:let g=r[n].slice(u,c);W(g,lunr.tokenizer.separator,(l,m)=>{if(typeof lunr.segmenter!="undefined"){let x=g.slice(l,m);if(/^[MHIK]$/.test(lunr.segmenter.ctype_(x))){let v=lunr.segmenter.segment(x);for(let d=0,y=0;dr){return t.trim().split(/"([^"]+)"/g).map((r,n)=>n&1?r.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):r).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").split(/\s+/g).reduce((r,n)=>{let i=e(n);return[...r,...Array.isArray(i)?i:[i]]},[]).map(r=>/([~^]$)/.test(r)?`${r}1`:r).map(r=>/(^[+-]|[~^]\d+$)/.test(r)?r:`${r}*`).join(" ")}function ue(t){return ae(t,e=>{let r=[],n=new lunr.QueryLexer(e);n.run();for(let{type:i,str:s,start:o,end:a}of n.lexemes)switch(i){case"FIELD":["title","text","tags"].includes(s)||(e=[e.slice(0,a)," ",e.slice(a+1)].join(""));break;case"TERM":W(s,lunr.tokenizer.separator,(...u)=>{r.push([e.slice(0,o),s.slice(...u),e.slice(a)].join(""))})}return r})}function ce(t){let e=new lunr.Query(["title","text","tags"]);new lunr.QueryParser(t,e).parse();for(let n of e.clauses)n.usePipeline=!0,n.term.startsWith("*")&&(n.wildcard=lunr.Query.wildcard.LEADING,n.term=n.term.slice(1)),n.term.endsWith("*")&&(n.wildcard=lunr.Query.wildcard.TRAILING,n.term=n.term.slice(0,-1));return e.clauses}function le(t,e){var i;let r=new Set(t),n={};for(let s=0;s0;){let o=i[--s];for(let u=1;un[o]-u&&(r.add(t.slice(o,o+u)),i[s++]=o+u);let a=o+n[o];n[a]&&ar=>{if(typeof r[e]=="undefined")return;let n=[r.location,e].join(":");return t.set(n,lunr.tokenizer.table=[]),r[e]}}function Re(t,e){let[r,n]=[new Set(t),new Set(e)];return[...new Set([...r].filter(i=>!n.has(i)))]}var H=class{constructor({config:e,docs:r,options:n}){let i=Oe(this.table=new Map);this.map=ne(r),this.options=n,this.index=lunr(function(){this.metadataWhitelist=["position"],this.b(0),e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang)),this.tokenizer=oe,lunr.tokenizer.separator=new RegExp(e.separator),lunr.segmenter="TinySegmenter"in lunr?new lunr.TinySegmenter:void 0;let s=Re(["trimmer","stopWordFilter","stemmer"],e.pipeline);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.ref("location"),this.field("title",{boost:1e3,extractor:i("title")}),this.field("text",{boost:1,extractor:i("text")}),this.field("tags",{boost:1e6,extractor:i("tags")});for(let o of r)this.add(o,{boost:o.boost})})}search(e){if(e=e.replace(new RegExp("\\p{sc=Han}+","gu"),s=>[...he(s,this.index.invertedIndex)].join("* ")),e=ue(e),!e)return{items:[]};let r=ce(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED),n=this.index.search(e).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.map.get(o);if(typeof c!="undefined"){c=A({},c),c.tags&&(c.tags=[...c.tags]);let f=le(r,Object.keys(u.metadata));for(let l of this.index.fields){if(typeof c[l]=="undefined")continue;let m=[];for(let d of Object.values(u.metadata))typeof d[l]!="undefined"&&m.push(...d[l].position);if(!m.length)continue;let x=this.table.get([c.location,l].join(":")),v=Array.isArray(c[l])?q:se;c[l]=v(c[l],x,m,l!=="text")}let g=+!c.parent+Object.values(f).filter(l=>l).length/Object.keys(f).length;s.push(G(A({},c),{score:a*(1+Z(g,2)),terms:f}))}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.map.get(o.location);if(typeof a!="undefined"){let u=a.parent?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map);for(let[s,o]of n)if(!o.find(a=>a.location===s)){let a=this.map.get(s);o.push(G(A({},a),{score:0,terms:{}}))}let i;if(this.options.suggest){let s=this.index.query(o=>{for(let a of r)o.term(a.term,{fields:["title"],presence:lunr.Query.presence.REQUIRED,wildcard:lunr.Query.wildcard.TRAILING})});i=s.length?Object.keys(s[0].matchData.metadata):[]}return A({items:[...n.values()]},typeof i!="undefined"&&{suggest:i})}};var fe;function Ie(t){return B(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=re("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang){switch(n){case"ja":r.push(`${e}/tinyseg.js`);break;case"hi":case"th":r.push(`${e}/wordcut.js`);break}n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`)}t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Fe(t){return B(this,null,function*(){switch(t.type){case 0:return yield Ie(t.data.config),fe=new H(t.data),{type:1};case 2:let e=t.data;try{return{type:3,data:fe.search(e)}}catch(r){return console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`),console.warn(r),{type:3,data:{items:[]}}}default:throw new TypeError("Invalid message type")}})}self.lunr=de.default;addEventListener("message",t=>B(void 0,null,function*(){postMessage(yield Fe(t.data))}));})();
-//# sourceMappingURL=search.07f07601.min.js.map
+ */t.Builder=function(){this._ref="id",this._fields=Object.create(null),this._documents=Object.create(null),this.invertedIndex=Object.create(null),this.fieldTermFrequencies={},this.fieldLengths={},this.tokenizer=t.tokenizer,this.pipeline=new t.Pipeline,this.searchPipeline=new t.Pipeline,this.documentCount=0,this._b=.75,this._k1=1.2,this.termIndex=0,this.metadataWhitelist=[]},t.Builder.prototype.ref=function(e){this._ref=e},t.Builder.prototype.field=function(e,r){if(/\//.test(e))throw new RangeError("Field '"+e+"' contains illegal character '/'");this._fields[e]=r||{}},t.Builder.prototype.b=function(e){e<0?this._b=0:e>1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ee=="object"?te.exports=r():e.lunr=r()}(this,function(){return t})})()});var Y=Pe(re());function ne(t,e=document){let r=ke(t,e);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${t}" to be present`);return r}function ke(t,e=document){return e.querySelector(t)||void 0}Object.entries||(Object.entries=function(t){let e=[];for(let r of Object.keys(t))e.push([r,t[r]]);return e});Object.values||(Object.values=function(t){let e=[];for(let r of Object.keys(t))e.push(t[r]);return e});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(t,e){typeof t=="object"?(this.scrollLeft=t.left,this.scrollTop=t.top):(this.scrollLeft=t,this.scrollTop=e)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...t){let e=this.parentNode;if(e){t.length===0&&e.removeChild(this);for(let r=t.length-1;r>=0;r--){let n=t[r];typeof n=="string"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?e.insertBefore(this.previousSibling,n):e.replaceChild(n,this)}}}));function ie(t){let e=new Map;for(let r of t){let[n]=r.location.split("#"),i=e.get(n);typeof i=="undefined"?e.set(n,r):(e.set(r.location,r),r.parent=i)}return e}function W(t,e,r){var s;e=new RegExp(e,"g");let n,i=0;do{n=e.exec(t);let o=(s=n==null?void 0:n.index)!=null?s:t.length;if(in?e(r,1,n,n=i):t.charAt(i)===">"&&(t.charAt(n+1)==="/"?--s===0&&e(r++,2,n,i+1):t.charAt(i-1)!=="/"&&s++===0&&e(r,0,n,i+1),n=i+1);i>n&&e(r,1,n,i)}function oe(t,e,r,n=!1){return q([t],e,r,n).pop()}function q(t,e,r,n=!1){let i=[0];for(let s=1;s>>2&1023,c=a[0]>>>12;i.push(+(u>c)+i[i.length-1])}return t.map((s,o)=>{let a=0,u=new Map;for(let f of r.sort((g,l)=>g-l)){let g=f&1048575,l=f>>>20;if(i[l]!==o)continue;let m=u.get(l);typeof m=="undefined"&&u.set(l,m=[]),m.push(g)}if(u.size===0)return s;let c=[];for(let[f,g]of u){let l=e[f],m=l[0]>>>12,x=l[l.length-1]>>>12,v=l[l.length-1]>>>2&1023;n&&m>a&&c.push(s.slice(a,m));let d=s.slice(m,x+v);for(let y of g.sort((b,E)=>E-b)){let b=(l[y]>>>12)-m,E=(l[y]>>>2&1023)+b;d=[d.slice(0,b),"",d.slice(b,E),"",d.slice(E)].join("")}if(a=x+v,c.push(d)===2)break}return n&&a{var f;switch(i[f=o+=s]||(i[f]=[]),a){case 0:case 2:i[o].push(u<<12|c-u<<2|a);break;case 1:let g=r[n].slice(u,c);W(g,lunr.tokenizer.separator,(l,m)=>{if(typeof lunr.segmenter!="undefined"){let x=g.slice(l,m);if(/^[MHIK]$/.test(lunr.segmenter.ctype_(x))){let v=lunr.segmenter.segment(x);for(let d=0,y=0;dr){return t.trim().split(/"([^"]+)"/g).map((r,n)=>n&1?r.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):r).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").split(/\s+/g).reduce((r,n)=>{let i=e(n);return[...r,...Array.isArray(i)?i:[i]]},[]).map(r=>/([~^]$)/.test(r)?`${r}1`:r).map(r=>/(^[+-]|[~^]\d+$)/.test(r)?r:`${r}*`).join(" ")}function ce(t){return ue(t,e=>{let r=[],n=new lunr.QueryLexer(e);n.run();for(let{type:i,str:s,start:o,end:a}of n.lexemes)switch(i){case"FIELD":["title","text","tags"].includes(s)||(e=[e.slice(0,a)," ",e.slice(a+1)].join(""));break;case"TERM":W(s,lunr.tokenizer.separator,(...u)=>{r.push([e.slice(0,o),s.slice(...u),e.slice(a)].join(""))})}return r})}function le(t){let e=new lunr.Query(["title","text","tags"]);new lunr.QueryParser(t,e).parse();for(let n of e.clauses)n.usePipeline=!0,n.term.startsWith("*")&&(n.wildcard=lunr.Query.wildcard.LEADING,n.term=n.term.slice(1)),n.term.endsWith("*")&&(n.wildcard=lunr.Query.wildcard.TRAILING,n.term=n.term.slice(0,-1));return e.clauses}function he(t,e){var i;let r=new Set(t),n={};for(let s=0;s0;){let o=i[--s];for(let u=1;un[o]-u&&(r.add(t.slice(o,o+u)),i[s++]=o+u);let a=o+n[o];n[a]&&ar=>{if(typeof r[e]=="undefined")return;let n=[r.location,e].join(":");return t.set(n,lunr.tokenizer.table=[]),r[e]}}function Re(t,e){let[r,n]=[new Set(t),new Set(e)];return[...new Set([...r].filter(i=>!n.has(i)))]}var H=class{constructor({config:e,docs:r,options:n}){let i=Oe(this.table=new Map);this.map=ie(r),this.options=n,this.index=lunr(function(){this.metadataWhitelist=["position"],this.b(0),e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang)),this.tokenizer=ae,lunr.tokenizer.separator=new RegExp(e.separator),lunr.segmenter="TinySegmenter"in lunr?new lunr.TinySegmenter:void 0;let s=Re(["trimmer","stopWordFilter","stemmer"],e.pipeline);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.ref("location"),this.field("title",{boost:1e3,extractor:i("title")}),this.field("text",{boost:1,extractor:i("text")}),this.field("tags",{boost:1e6,extractor:i("tags")});for(let o of r)this.add(o,{boost:o.boost})})}search(e){if(e=e.replace(new RegExp("\\p{sc=Han}+","gu"),s=>[...fe(s,this.index.invertedIndex)].join("* ")),e=ce(e),!e)return{items:[]};let r=le(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED),n=this.index.search(e).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.map.get(o);if(typeof c!="undefined"){c=A({},c),c.tags&&(c.tags=[...c.tags]);let f=he(r,Object.keys(u.metadata));for(let l of this.index.fields){if(typeof c[l]=="undefined")continue;let m=[];for(let d of Object.values(u.metadata))typeof d[l]!="undefined"&&m.push(...d[l].position);if(!m.length)continue;let x=this.table.get([c.location,l].join(":")),v=Array.isArray(c[l])?q:oe;c[l]=v(c[l],x,m,l!=="text")}let g=+!c.parent+Object.values(f).filter(l=>l).length/Object.keys(f).length;s.push(G(A({},c),{score:a*(1+K(g,2)),terms:f}))}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.map.get(o.location);if(typeof a!="undefined"){let u=a.parent?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map);for(let[s,o]of n)if(!o.find(a=>a.location===s)){let a=this.map.get(s);o.push(G(A({},a),{score:0,terms:{}}))}let i;if(this.options.suggest){let s=this.index.query(o=>{for(let a of r)o.term(a.term,{fields:["title"],presence:lunr.Query.presence.REQUIRED,wildcard:lunr.Query.wildcard.TRAILING})});i=s.length?Object.keys(s[0].matchData.metadata):[]}return A({items:[...n.values()]},typeof i!="undefined"&&{suggest:i})}};var de;function Ie(t){return B(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=ne("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang){switch(n){case"ja":r.push(`${e}/tinyseg.js`);break;case"hi":case"th":r.push(`${e}/wordcut.js`);break}n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`)}t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Fe(t){return B(this,null,function*(){switch(t.type){case 0:return yield Ie(t.data.config),de=new H(t.data),{type:1};case 2:let e=t.data;try{return{type:3,data:de.search(e)}}catch(r){return console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`),console.warn(r),{type:3,data:{items:[]}}}default:throw new TypeError("Invalid message type")}})}self.lunr=Y.default;Y.default.utils.warn=console.warn;addEventListener("message",t=>B(void 0,null,function*(){postMessage(yield Fe(t.data))}));})();
+//# sourceMappingURL=search.6ce7567c.min.js.map
diff --git a/assets/javascripts/workers/search.07f07601.min.js.map b/assets/javascripts/workers/search.6ce7567c.min.js.map
similarity index 78%
rename from assets/javascripts/workers/search.07f07601.min.js.map
rename to assets/javascripts/workers/search.6ce7567c.min.js.map
index 7fdd4d0..e7c69d2 100644
--- a/assets/javascripts/workers/search.07f07601.min.js.map
+++ b/assets/javascripts/workers/search.6ce7567c.min.js.map
@@ -1,7 +1,7 @@
{
"version": 3,
"sources": ["node_modules/lunr/lunr.js", "src/templates/assets/javascripts/integrations/search/worker/main/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/polyfills/index.ts", "src/templates/assets/javascripts/integrations/search/config/index.ts", "src/templates/assets/javascripts/integrations/search/internal/_/index.ts", "src/templates/assets/javascripts/integrations/search/internal/extract/index.ts", "src/templates/assets/javascripts/integrations/search/internal/highlight/index.ts", "src/templates/assets/javascripts/integrations/search/internal/tokenize/index.ts", "src/templates/assets/javascripts/integrations/search/query/transform/index.ts", "src/templates/assets/javascripts/integrations/search/query/_/index.ts", "src/templates/assets/javascripts/integrations/search/query/segment/index.ts", "src/templates/assets/javascripts/integrations/search/_/index.ts"],
- "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n * this.field('title')\n * this.field('body')\n * this.ref('id')\n *\n * documents.forEach(function (doc) {\n * this.add(doc)\n * }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n var builder = new lunr.Builder\n\n builder.pipeline.add(\n lunr.trimmer,\n lunr.stopWordFilter,\n lunr.stemmer\n )\n\n builder.searchPipeline.add(\n lunr.stemmer\n )\n\n config.call(builder, builder)\n return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n /* eslint-disable no-console */\n return function (message) {\n if (global.console && console.warn) {\n console.warn(message)\n }\n }\n /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n if (obj === void 0 || obj === null) {\n return \"\"\n } else {\n return obj.toString()\n }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n var clone = Object.create(null),\n keys = Object.keys(obj)\n\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i],\n val = obj[key]\n\n if (Array.isArray(val)) {\n clone[key] = val.slice()\n continue\n }\n\n if (typeof val === 'string' ||\n typeof val === 'number' ||\n typeof val === 'boolean') {\n clone[key] = val\n continue\n }\n\n throw new TypeError(\"clone is not deep and does not support nested objects\")\n }\n\n return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n this.docRef = docRef\n this.fieldName = fieldName\n this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n var n = s.indexOf(lunr.FieldRef.joiner)\n\n if (n === -1) {\n throw \"malformed field ref string\"\n }\n\n var fieldRef = s.slice(0, n),\n docRef = s.slice(n + 1)\n\n return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n if (this._stringValue == undefined) {\n this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n }\n\n return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n this.elements = Object.create(null)\n\n if (elements) {\n this.length = elements.length\n\n for (var i = 0; i < this.length; i++) {\n this.elements[elements[i]] = true\n }\n } else {\n this.length = 0\n }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n intersect: function (other) {\n return other\n },\n\n union: function () {\n return this\n },\n\n contains: function () {\n return true\n }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n intersect: function () {\n return this\n },\n\n union: function (other) {\n return other\n },\n\n contains: function () {\n return false\n }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n var a, b, elements, intersection = []\n\n if (other === lunr.Set.complete) {\n return this\n }\n\n if (other === lunr.Set.empty) {\n return other\n }\n\n if (this.length < other.length) {\n a = this\n b = other\n } else {\n a = other\n b = this\n }\n\n elements = Object.keys(a.elements)\n\n for (var i = 0; i < elements.length; i++) {\n var element = elements[i]\n if (element in b.elements) {\n intersection.push(element)\n }\n }\n\n return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n if (other === lunr.Set.complete) {\n return lunr.Set.complete\n }\n\n if (other === lunr.Set.empty) {\n return this\n }\n\n return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n var documentsWithTerm = 0\n\n for (var fieldName in posting) {\n if (fieldName == '_index') continue // Ignore the term index, its not a field\n documentsWithTerm += Object.keys(posting[fieldName]).length\n }\n\n var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n this.str = str || \"\"\n this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n * return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n this.str = fn(this.str, this.metadata)\n return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n fn = fn || function (s) { return s }\n return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n if (obj == null || obj == undefined) {\n return []\n }\n\n if (Array.isArray(obj)) {\n return obj.map(function (t) {\n return new lunr.Token(\n lunr.utils.asString(t).toLowerCase(),\n lunr.utils.clone(metadata)\n )\n })\n }\n\n var str = obj.toString().toLowerCase(),\n len = str.length,\n tokens = []\n\n for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n var char = str.charAt(sliceEnd),\n sliceLength = sliceEnd - sliceStart\n\n if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n if (sliceLength > 0) {\n var tokenMetadata = lunr.utils.clone(metadata) || {}\n tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n tokenMetadata[\"index\"] = tokens.length\n\n tokens.push(\n new lunr.Token (\n str.slice(sliceStart, sliceEnd),\n tokenMetadata\n )\n )\n }\n\n sliceStart = sliceEnd + 1\n }\n\n }\n\n return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n if (label in this.registeredFunctions) {\n lunr.utils.warn('Overwriting existing registered function: ' + label)\n }\n\n fn.label = label\n lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n if (!isRegistered) {\n lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n var pipeline = new lunr.Pipeline\n\n serialised.forEach(function (fnName) {\n var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n if (fn) {\n pipeline.add(fn)\n } else {\n throw new Error('Cannot load unregistered function: ' + fnName)\n }\n })\n\n return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n var fns = Array.prototype.slice.call(arguments)\n\n fns.forEach(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n this._stack.push(fn)\n }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n pos = pos + 1\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n var pos = this._stack.indexOf(fn)\n if (pos == -1) {\n return\n }\n\n this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n var stackLength = this._stack.length\n\n for (var i = 0; i < stackLength; i++) {\n var fn = this._stack[i]\n var memo = []\n\n for (var j = 0; j < tokens.length; j++) {\n var result = fn(tokens[j], j, tokens)\n\n if (result === null || result === void 0 || result === '') continue\n\n if (Array.isArray(result)) {\n for (var k = 0; k < result.length; k++) {\n memo.push(result[k])\n }\n } else {\n memo.push(result)\n }\n }\n\n tokens = memo\n }\n\n return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n var token = new lunr.Token (str, metadata)\n\n return this.run([token]).map(function (t) {\n return t.toString()\n })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n return this._stack.map(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n return fn.label\n })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n this._magnitude = 0\n this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n // For an empty vector the tuple can be inserted at the beginning\n if (this.elements.length == 0) {\n return 0\n }\n\n var start = 0,\n end = this.elements.length / 2,\n sliceLength = end - start,\n pivotPoint = Math.floor(sliceLength / 2),\n pivotIndex = this.elements[pivotPoint * 2]\n\n while (sliceLength > 1) {\n if (pivotIndex < index) {\n start = pivotPoint\n }\n\n if (pivotIndex > index) {\n end = pivotPoint\n }\n\n if (pivotIndex == index) {\n break\n }\n\n sliceLength = end - start\n pivotPoint = start + Math.floor(sliceLength / 2)\n pivotIndex = this.elements[pivotPoint * 2]\n }\n\n if (pivotIndex == index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex > index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex < index) {\n return (pivotPoint + 1) * 2\n }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n this.upsert(insertIdx, val, function () {\n throw \"duplicate index\"\n })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n this._magnitude = 0\n var position = this.positionForIndex(insertIdx)\n\n if (this.elements[position] == insertIdx) {\n this.elements[position + 1] = fn(this.elements[position + 1], val)\n } else {\n this.elements.splice(position, 0, insertIdx, val)\n }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n if (this._magnitude) return this._magnitude\n\n var sumOfSquares = 0,\n elementsLength = this.elements.length\n\n for (var i = 1; i < elementsLength; i += 2) {\n var val = this.elements[i]\n sumOfSquares += val * val\n }\n\n return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n var dotProduct = 0,\n a = this.elements, b = otherVector.elements,\n aLen = a.length, bLen = b.length,\n aVal = 0, bVal = 0,\n i = 0, j = 0\n\n while (i < aLen && j < bLen) {\n aVal = a[i], bVal = b[j]\n if (aVal < bVal) {\n i += 2\n } else if (aVal > bVal) {\n j += 2\n } else if (aVal == bVal) {\n dotProduct += a[i + 1] * b[j + 1]\n i += 2\n j += 2\n }\n }\n\n return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n var output = new Array (this.elements.length / 2)\n\n for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n output[j] = this.elements[i]\n }\n\n return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n var step2list = {\n \"ational\" : \"ate\",\n \"tional\" : \"tion\",\n \"enci\" : \"ence\",\n \"anci\" : \"ance\",\n \"izer\" : \"ize\",\n \"bli\" : \"ble\",\n \"alli\" : \"al\",\n \"entli\" : \"ent\",\n \"eli\" : \"e\",\n \"ousli\" : \"ous\",\n \"ization\" : \"ize\",\n \"ation\" : \"ate\",\n \"ator\" : \"ate\",\n \"alism\" : \"al\",\n \"iveness\" : \"ive\",\n \"fulness\" : \"ful\",\n \"ousness\" : \"ous\",\n \"aliti\" : \"al\",\n \"iviti\" : \"ive\",\n \"biliti\" : \"ble\",\n \"logi\" : \"log\"\n },\n\n step3list = {\n \"icate\" : \"ic\",\n \"ative\" : \"\",\n \"alize\" : \"al\",\n \"iciti\" : \"ic\",\n \"ical\" : \"ic\",\n \"ful\" : \"\",\n \"ness\" : \"\"\n },\n\n c = \"[^aeiou]\", // consonant\n v = \"[aeiouy]\", // vowel\n C = c + \"[^aeiouy]*\", // consonant sequence\n V = v + \"[aeiou]*\", // vowel sequence\n\n mgr0 = \"^(\" + C + \")?\" + V + C, // [C]VC... is m>0\n meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\", // [C]VC[V] is m=1\n mgr1 = \"^(\" + C + \")?\" + V + C + V + C, // [C]VCVC... is m>1\n s_v = \"^(\" + C + \")?\" + v; // vowel in stem\n\n var re_mgr0 = new RegExp(mgr0);\n var re_mgr1 = new RegExp(mgr1);\n var re_meq1 = new RegExp(meq1);\n var re_s_v = new RegExp(s_v);\n\n var re_1a = /^(.+?)(ss|i)es$/;\n var re2_1a = /^(.+?)([^s])s$/;\n var re_1b = /^(.+?)eed$/;\n var re2_1b = /^(.+?)(ed|ing)$/;\n var re_1b_2 = /.$/;\n var re2_1b_2 = /(at|bl|iz)$/;\n var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var re_1c = /^(.+?[^aeiou])y$/;\n var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n var re_5 = /^(.+?)e$/;\n var re_5_1 = /ll$/;\n var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var porterStemmer = function porterStemmer(w) {\n var stem,\n suffix,\n firstch,\n re,\n re2,\n re3,\n re4;\n\n if (w.length < 3) { return w; }\n\n firstch = w.substr(0,1);\n if (firstch == \"y\") {\n w = firstch.toUpperCase() + w.substr(1);\n }\n\n // Step 1a\n re = re_1a\n re2 = re2_1a;\n\n if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n // Step 1b\n re = re_1b;\n re2 = re2_1b;\n if (re.test(w)) {\n var fp = re.exec(w);\n re = re_mgr0;\n if (re.test(fp[1])) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1];\n re2 = re_s_v;\n if (re2.test(stem)) {\n w = stem;\n re2 = re2_1b_2;\n re3 = re3_1b_2;\n re4 = re4_1b_2;\n if (re2.test(w)) { w = w + \"e\"; }\n else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n else if (re4.test(w)) { w = w + \"e\"; }\n }\n }\n\n // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n re = re_1c;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n w = stem + \"i\";\n }\n\n // Step 2\n re = re_2;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step2list[suffix];\n }\n }\n\n // Step 3\n re = re_3;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step3list[suffix];\n }\n }\n\n // Step 4\n re = re_4;\n re2 = re2_4;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n if (re.test(stem)) {\n w = stem;\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1] + fp[2];\n re2 = re_mgr1;\n if (re2.test(stem)) {\n w = stem;\n }\n }\n\n // Step 5\n re = re_5;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n re2 = re_meq1;\n re3 = re3_5;\n if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n w = stem;\n }\n }\n\n re = re_5_1;\n re2 = re_mgr1;\n if (re.test(w) && re2.test(w)) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n\n // and turn initial Y back to y\n\n if (firstch == \"y\") {\n w = firstch.toLowerCase() + w.substr(1);\n }\n\n return w;\n };\n\n return function (token) {\n return token.update(porterStemmer);\n }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n var words = stopWords.reduce(function (memo, stopWord) {\n memo[stopWord] = stopWord\n return memo\n }, {})\n\n return function (token) {\n if (token && words[token.toString()] !== token.toString()) return token\n }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n 'a',\n 'able',\n 'about',\n 'across',\n 'after',\n 'all',\n 'almost',\n 'also',\n 'am',\n 'among',\n 'an',\n 'and',\n 'any',\n 'are',\n 'as',\n 'at',\n 'be',\n 'because',\n 'been',\n 'but',\n 'by',\n 'can',\n 'cannot',\n 'could',\n 'dear',\n 'did',\n 'do',\n 'does',\n 'either',\n 'else',\n 'ever',\n 'every',\n 'for',\n 'from',\n 'get',\n 'got',\n 'had',\n 'has',\n 'have',\n 'he',\n 'her',\n 'hers',\n 'him',\n 'his',\n 'how',\n 'however',\n 'i',\n 'if',\n 'in',\n 'into',\n 'is',\n 'it',\n 'its',\n 'just',\n 'least',\n 'let',\n 'like',\n 'likely',\n 'may',\n 'me',\n 'might',\n 'most',\n 'must',\n 'my',\n 'neither',\n 'no',\n 'nor',\n 'not',\n 'of',\n 'off',\n 'often',\n 'on',\n 'only',\n 'or',\n 'other',\n 'our',\n 'own',\n 'rather',\n 'said',\n 'say',\n 'says',\n 'she',\n 'should',\n 'since',\n 'so',\n 'some',\n 'than',\n 'that',\n 'the',\n 'their',\n 'them',\n 'then',\n 'there',\n 'these',\n 'they',\n 'this',\n 'tis',\n 'to',\n 'too',\n 'twas',\n 'us',\n 'wants',\n 'was',\n 'we',\n 'were',\n 'what',\n 'when',\n 'where',\n 'which',\n 'while',\n 'who',\n 'whom',\n 'why',\n 'will',\n 'with',\n 'would',\n 'yet',\n 'you',\n 'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n return token.update(function (s) {\n return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n this.final = false\n this.edges = {}\n this.id = lunr.TokenSet._nextId\n lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n var builder = new lunr.TokenSet.Builder\n\n for (var i = 0, len = arr.length; i < len; i++) {\n builder.insert(arr[i])\n }\n\n builder.finish()\n return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n if ('editDistance' in clause) {\n return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n } else {\n return lunr.TokenSet.fromString(clause.term)\n }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n var root = new lunr.TokenSet\n\n var stack = [{\n node: root,\n editsRemaining: editDistance,\n str: str\n }]\n\n while (stack.length) {\n var frame = stack.pop()\n\n // no edit\n if (frame.str.length > 0) {\n var char = frame.str.charAt(0),\n noEditNode\n\n if (char in frame.node.edges) {\n noEditNode = frame.node.edges[char]\n } else {\n noEditNode = new lunr.TokenSet\n frame.node.edges[char] = noEditNode\n }\n\n if (frame.str.length == 1) {\n noEditNode.final = true\n }\n\n stack.push({\n node: noEditNode,\n editsRemaining: frame.editsRemaining,\n str: frame.str.slice(1)\n })\n }\n\n if (frame.editsRemaining == 0) {\n continue\n }\n\n // insertion\n if (\"*\" in frame.node.edges) {\n var insertionNode = frame.node.edges[\"*\"]\n } else {\n var insertionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = insertionNode\n }\n\n if (frame.str.length == 0) {\n insertionNode.final = true\n }\n\n stack.push({\n node: insertionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str\n })\n\n // deletion\n // can only do a deletion if we have enough edits remaining\n // and if there are characters left to delete in the string\n if (frame.str.length > 1) {\n stack.push({\n node: frame.node,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // deletion\n // just removing the last character from the str\n if (frame.str.length == 1) {\n frame.node.final = true\n }\n\n // substitution\n // can only do a substitution if we have enough edits remaining\n // and if there are characters left to substitute\n if (frame.str.length >= 1) {\n if (\"*\" in frame.node.edges) {\n var substitutionNode = frame.node.edges[\"*\"]\n } else {\n var substitutionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = substitutionNode\n }\n\n if (frame.str.length == 1) {\n substitutionNode.final = true\n }\n\n stack.push({\n node: substitutionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // transposition\n // can only do a transposition if there are edits remaining\n // and there are enough characters to transpose\n if (frame.str.length > 1) {\n var charA = frame.str.charAt(0),\n charB = frame.str.charAt(1),\n transposeNode\n\n if (charB in frame.node.edges) {\n transposeNode = frame.node.edges[charB]\n } else {\n transposeNode = new lunr.TokenSet\n frame.node.edges[charB] = transposeNode\n }\n\n if (frame.str.length == 1) {\n transposeNode.final = true\n }\n\n stack.push({\n node: transposeNode,\n editsRemaining: frame.editsRemaining - 1,\n str: charA + frame.str.slice(2)\n })\n }\n }\n\n return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n var node = new lunr.TokenSet,\n root = node\n\n /*\n * Iterates through all characters within the passed string\n * appending a node for each character.\n *\n * When a wildcard character is found then a self\n * referencing edge is introduced to continually match\n * any number of any characters.\n */\n for (var i = 0, len = str.length; i < len; i++) {\n var char = str[i],\n final = (i == len - 1)\n\n if (char == \"*\") {\n node.edges[char] = node\n node.final = final\n\n } else {\n var next = new lunr.TokenSet\n next.final = final\n\n node.edges[char] = next\n node = next\n }\n }\n\n return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n var words = []\n\n var stack = [{\n prefix: \"\",\n node: this\n }]\n\n while (stack.length) {\n var frame = stack.pop(),\n edges = Object.keys(frame.node.edges),\n len = edges.length\n\n if (frame.node.final) {\n /* In Safari, at this point the prefix is sometimes corrupted, see:\n * https://github.com/olivernn/lunr.js/issues/279 Calling any\n * String.prototype method forces Safari to \"cast\" this string to what\n * it's supposed to be, fixing the bug. */\n frame.prefix.charAt(0)\n words.push(frame.prefix)\n }\n\n for (var i = 0; i < len; i++) {\n var edge = edges[i]\n\n stack.push({\n prefix: frame.prefix.concat(edge),\n node: frame.node.edges[edge]\n })\n }\n }\n\n return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n // NOTE: Using Object.keys here as this.edges is very likely\n // to enter 'hash-mode' with many keys being added\n //\n // avoiding a for-in loop here as it leads to the function\n // being de-optimised (at least in V8). From some simple\n // benchmarks the performance is comparable, but allowing\n // V8 to optimize may mean easy performance wins in the future.\n\n if (this._str) {\n return this._str\n }\n\n var str = this.final ? '1' : '0',\n labels = Object.keys(this.edges).sort(),\n len = labels.length\n\n for (var i = 0; i < len; i++) {\n var label = labels[i],\n node = this.edges[label]\n\n str = str + label + node.id\n }\n\n return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n var output = new lunr.TokenSet,\n frame = undefined\n\n var stack = [{\n qNode: b,\n output: output,\n node: this\n }]\n\n while (stack.length) {\n frame = stack.pop()\n\n // NOTE: As with the #toString method, we are using\n // Object.keys and a for loop instead of a for-in loop\n // as both of these objects enter 'hash' mode, causing\n // the function to be de-optimised in V8\n var qEdges = Object.keys(frame.qNode.edges),\n qLen = qEdges.length,\n nEdges = Object.keys(frame.node.edges),\n nLen = nEdges.length\n\n for (var q = 0; q < qLen; q++) {\n var qEdge = qEdges[q]\n\n for (var n = 0; n < nLen; n++) {\n var nEdge = nEdges[n]\n\n if (nEdge == qEdge || qEdge == '*') {\n var node = frame.node.edges[nEdge],\n qNode = frame.qNode.edges[qEdge],\n final = node.final && qNode.final,\n next = undefined\n\n if (nEdge in frame.output.edges) {\n // an edge already exists for this character\n // no need to create a new node, just set the finality\n // bit unless this node is already final\n next = frame.output.edges[nEdge]\n next.final = next.final || final\n\n } else {\n // no edge exists yet, must create one\n // set the finality bit and insert it\n // into the output\n next = new lunr.TokenSet\n next.final = final\n frame.output.edges[nEdge] = next\n }\n\n stack.push({\n qNode: qNode,\n output: next,\n node: node\n })\n }\n }\n }\n }\n\n return output\n}\nlunr.TokenSet.Builder = function () {\n this.previousWord = \"\"\n this.root = new lunr.TokenSet\n this.uncheckedNodes = []\n this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n var node,\n commonPrefix = 0\n\n if (word < this.previousWord) {\n throw new Error (\"Out of order word insertion\")\n }\n\n for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n if (word[i] != this.previousWord[i]) break\n commonPrefix++\n }\n\n this.minimize(commonPrefix)\n\n if (this.uncheckedNodes.length == 0) {\n node = this.root\n } else {\n node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n }\n\n for (var i = commonPrefix; i < word.length; i++) {\n var nextNode = new lunr.TokenSet,\n char = word[i]\n\n node.edges[char] = nextNode\n\n this.uncheckedNodes.push({\n parent: node,\n char: char,\n child: nextNode\n })\n\n node = nextNode\n }\n\n node.final = true\n this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n var node = this.uncheckedNodes[i],\n childKey = node.child.toString()\n\n if (childKey in this.minimizedNodes) {\n node.parent.edges[node.char] = this.minimizedNodes[childKey]\n } else {\n // Cache the key for this node since\n // we know it can't change anymore\n node.child._str = childKey\n\n this.minimizedNodes[childKey] = node.child\n }\n\n this.uncheckedNodes.pop()\n }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n this.invertedIndex = attrs.invertedIndex\n this.fieldVectors = attrs.fieldVectors\n this.tokenSet = attrs.tokenSet\n this.fields = attrs.fields\n this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example Simple single term query \n * hello\n * @example Multiple term query \n * hello world\n * @example term scoped to a field \n * title:hello\n * @example term with a boost of 10 \n * hello^10\n * @example term with an edit distance of 2 \n * hello~2\n * @example terms with presence modifiers \n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first. For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n return this.query(function (query) {\n var parser = new lunr.QueryParser(queryString, query)\n parser.parse()\n })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n // for each query clause\n // * process terms\n // * expand terms from token set\n // * find matching documents and metadata\n // * get document vectors\n // * score documents\n\n var query = new lunr.Query(this.fields),\n matchingFields = Object.create(null),\n queryVectors = Object.create(null),\n termFieldCache = Object.create(null),\n requiredMatches = Object.create(null),\n prohibitedMatches = Object.create(null)\n\n /*\n * To support field level boosts a query vector is created per\n * field. An empty vector is eagerly created to support negated\n * queries.\n */\n for (var i = 0; i < this.fields.length; i++) {\n queryVectors[this.fields[i]] = new lunr.Vector\n }\n\n fn.call(query, query)\n\n for (var i = 0; i < query.clauses.length; i++) {\n /*\n * Unless the pipeline has been disabled for this term, which is\n * the case for terms with wildcards, we need to pass the clause\n * term through the search pipeline. A pipeline returns an array\n * of processed terms. Pipeline functions may expand the passed\n * term, which means we may end up performing multiple index lookups\n * for a single query term.\n */\n var clause = query.clauses[i],\n terms = null,\n clauseMatches = lunr.Set.empty\n\n if (clause.usePipeline) {\n terms = this.pipeline.runString(clause.term, {\n fields: clause.fields\n })\n } else {\n terms = [clause.term]\n }\n\n for (var m = 0; m < terms.length; m++) {\n var term = terms[m]\n\n /*\n * Each term returned from the pipeline needs to use the same query\n * clause object, e.g. the same boost and or edit distance. The\n * simplest way to do this is to re-use the clause object but mutate\n * its term property.\n */\n clause.term = term\n\n /*\n * From the term in the clause we create a token set which will then\n * be used to intersect the indexes token set to get a list of terms\n * to lookup in the inverted index\n */\n var termTokenSet = lunr.TokenSet.fromClause(clause),\n expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n /*\n * If a term marked as required does not exist in the tokenSet it is\n * impossible for the search to return any matches. We set all the field\n * scoped required matches set to empty and stop examining any further\n * clauses.\n */\n if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = lunr.Set.empty\n }\n\n break\n }\n\n for (var j = 0; j < expandedTerms.length; j++) {\n /*\n * For each term get the posting and termIndex, this is required for\n * building the query vector.\n */\n var expandedTerm = expandedTerms[j],\n posting = this.invertedIndex[expandedTerm],\n termIndex = posting._index\n\n for (var k = 0; k < clause.fields.length; k++) {\n /*\n * For each field that this query term is scoped by (by default\n * all fields are in scope) we need to get all the document refs\n * that have this term in that field.\n *\n * The posting is the entry in the invertedIndex for the matching\n * term from above.\n */\n var field = clause.fields[k],\n fieldPosting = posting[field],\n matchingDocumentRefs = Object.keys(fieldPosting),\n termField = expandedTerm + \"/\" + field,\n matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n /*\n * if the presence of this term is required ensure that the matching\n * documents are added to the set of required matches for this clause.\n *\n */\n if (clause.presence == lunr.Query.presence.REQUIRED) {\n clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n if (requiredMatches[field] === undefined) {\n requiredMatches[field] = lunr.Set.complete\n }\n }\n\n /*\n * if the presence of this term is prohibited ensure that the matching\n * documents are added to the set of prohibited matches for this field,\n * creating that set if it does not yet exist.\n */\n if (clause.presence == lunr.Query.presence.PROHIBITED) {\n if (prohibitedMatches[field] === undefined) {\n prohibitedMatches[field] = lunr.Set.empty\n }\n\n prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n /*\n * Prohibited matches should not be part of the query vector used for\n * similarity scoring and no metadata should be extracted so we continue\n * to the next field\n */\n continue\n }\n\n /*\n * The query field vector is populated using the termIndex found for\n * the term and a unit value with the appropriate boost applied.\n * Using upsert because there could already be an entry in the vector\n * for the term we are working with. In that case we just add the scores\n * together.\n */\n queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n /**\n * If we've already seen this term, field combo then we've already collected\n * the matching documents and metadata, no need to go through all that again\n */\n if (termFieldCache[termField]) {\n continue\n }\n\n for (var l = 0; l < matchingDocumentRefs.length; l++) {\n /*\n * All metadata for this term/field/document triple\n * are then extracted and collected into an instance\n * of lunr.MatchData ready to be returned in the query\n * results\n */\n var matchingDocumentRef = matchingDocumentRefs[l],\n matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n metadata = fieldPosting[matchingDocumentRef],\n fieldMatch\n\n if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n } else {\n fieldMatch.add(expandedTerm, field, metadata)\n }\n\n }\n\n termFieldCache[termField] = true\n }\n }\n }\n\n /**\n * If the presence was required we need to update the requiredMatches field sets.\n * We do this after all fields for the term have collected their matches because\n * the clause terms presence is required in _any_ of the fields not _all_ of the\n * fields.\n */\n if (clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n }\n }\n }\n\n /**\n * Need to combine the field scoped required and prohibited\n * matching documents into a global set of required and prohibited\n * matches\n */\n var allRequiredMatches = lunr.Set.complete,\n allProhibitedMatches = lunr.Set.empty\n\n for (var i = 0; i < this.fields.length; i++) {\n var field = this.fields[i]\n\n if (requiredMatches[field]) {\n allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n }\n\n if (prohibitedMatches[field]) {\n allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n }\n }\n\n var matchingFieldRefs = Object.keys(matchingFields),\n results = [],\n matches = Object.create(null)\n\n /*\n * If the query is negated (contains only prohibited terms)\n * we need to get _all_ fieldRefs currently existing in the\n * index. This is only done when we know that the query is\n * entirely prohibited terms to avoid any cost of getting all\n * fieldRefs unnecessarily.\n *\n * Additionally, blank MatchData must be created to correctly\n * populate the results.\n */\n if (query.isNegated()) {\n matchingFieldRefs = Object.keys(this.fieldVectors)\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n var matchingFieldRef = matchingFieldRefs[i]\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n matchingFields[matchingFieldRef] = new lunr.MatchData\n }\n }\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n /*\n * Currently we have document fields that match the query, but we\n * need to return documents. The matchData and scores are combined\n * from multiple fields belonging to the same document.\n *\n * Scores are calculated by field, using the query vectors created\n * above, and combined into a final document score using addition.\n */\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n docRef = fieldRef.docRef\n\n if (!allRequiredMatches.contains(docRef)) {\n continue\n }\n\n if (allProhibitedMatches.contains(docRef)) {\n continue\n }\n\n var fieldVector = this.fieldVectors[fieldRef],\n score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n docMatch\n\n if ((docMatch = matches[docRef]) !== undefined) {\n docMatch.score += score\n docMatch.matchData.combine(matchingFields[fieldRef])\n } else {\n var match = {\n ref: docRef,\n score: score,\n matchData: matchingFields[fieldRef]\n }\n matches[docRef] = match\n results.push(match)\n }\n }\n\n /*\n * Sort the results objects by score, highest first.\n */\n return results.sort(function (a, b) {\n return b.score - a.score\n })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n var invertedIndex = Object.keys(this.invertedIndex)\n .sort()\n .map(function (term) {\n return [term, this.invertedIndex[term]]\n }, this)\n\n var fieldVectors = Object.keys(this.fieldVectors)\n .map(function (ref) {\n return [ref, this.fieldVectors[ref].toJSON()]\n }, this)\n\n return {\n version: lunr.version,\n fields: this.fields,\n fieldVectors: fieldVectors,\n invertedIndex: invertedIndex,\n pipeline: this.pipeline.toJSON()\n }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n var attrs = {},\n fieldVectors = {},\n serializedVectors = serializedIndex.fieldVectors,\n invertedIndex = Object.create(null),\n serializedInvertedIndex = serializedIndex.invertedIndex,\n tokenSetBuilder = new lunr.TokenSet.Builder,\n pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n if (serializedIndex.version != lunr.version) {\n lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n }\n\n for (var i = 0; i < serializedVectors.length; i++) {\n var tuple = serializedVectors[i],\n ref = tuple[0],\n elements = tuple[1]\n\n fieldVectors[ref] = new lunr.Vector(elements)\n }\n\n for (var i = 0; i < serializedInvertedIndex.length; i++) {\n var tuple = serializedInvertedIndex[i],\n term = tuple[0],\n posting = tuple[1]\n\n tokenSetBuilder.insert(term)\n invertedIndex[term] = posting\n }\n\n tokenSetBuilder.finish()\n\n attrs.fields = serializedIndex.fields\n\n attrs.fieldVectors = fieldVectors\n attrs.invertedIndex = invertedIndex\n attrs.tokenSet = tokenSetBuilder.root\n attrs.pipeline = pipeline\n\n return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n this._ref = \"id\"\n this._fields = Object.create(null)\n this._documents = Object.create(null)\n this.invertedIndex = Object.create(null)\n this.fieldTermFrequencies = {}\n this.fieldLengths = {}\n this.tokenizer = lunr.tokenizer\n this.pipeline = new lunr.Pipeline\n this.searchPipeline = new lunr.Pipeline\n this.documentCount = 0\n this._b = 0.75\n this._k1 = 1.2\n this.termIndex = 0\n this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example Extracting a nested field \n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n if (/\\//.test(fieldName)) {\n throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n }\n\n this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n if (number < 0) {\n this._b = 0\n } else if (number > 1) {\n this._b = 1\n } else {\n this._b = number\n }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n var docRef = doc[this._ref],\n fields = Object.keys(this._fields)\n\n this._documents[docRef] = attributes || {}\n this.documentCount += 1\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i],\n extractor = this._fields[fieldName].extractor,\n field = extractor ? extractor(doc) : doc[fieldName],\n tokens = this.tokenizer(field, {\n fields: [fieldName]\n }),\n terms = this.pipeline.run(tokens),\n fieldRef = new lunr.FieldRef (docRef, fieldName),\n fieldTerms = Object.create(null)\n\n this.fieldTermFrequencies[fieldRef] = fieldTerms\n this.fieldLengths[fieldRef] = 0\n\n // store the length of this field for this document\n this.fieldLengths[fieldRef] += terms.length\n\n // calculate term frequencies for this field\n for (var j = 0; j < terms.length; j++) {\n var term = terms[j]\n\n if (fieldTerms[term] == undefined) {\n fieldTerms[term] = 0\n }\n\n fieldTerms[term] += 1\n\n // add to inverted index\n // create an initial posting if one doesn't exist\n if (this.invertedIndex[term] == undefined) {\n var posting = Object.create(null)\n posting[\"_index\"] = this.termIndex\n this.termIndex += 1\n\n for (var k = 0; k < fields.length; k++) {\n posting[fields[k]] = Object.create(null)\n }\n\n this.invertedIndex[term] = posting\n }\n\n // add an entry for this term/fieldName/docRef to the invertedIndex\n if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n }\n\n // store all whitelisted metadata about this token in the\n // inverted index\n for (var l = 0; l < this.metadataWhitelist.length; l++) {\n var metadataKey = this.metadataWhitelist[l],\n metadata = term.metadata[metadataKey]\n\n if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n }\n\n this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n }\n }\n\n }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n var fieldRefs = Object.keys(this.fieldLengths),\n numberOfFields = fieldRefs.length,\n accumulator = {},\n documentsWithField = {}\n\n for (var i = 0; i < numberOfFields; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n field = fieldRef.fieldName\n\n documentsWithField[field] || (documentsWithField[field] = 0)\n documentsWithField[field] += 1\n\n accumulator[field] || (accumulator[field] = 0)\n accumulator[field] += this.fieldLengths[fieldRef]\n }\n\n var fields = Object.keys(this._fields)\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i]\n accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n }\n\n this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n var fieldVectors = {},\n fieldRefs = Object.keys(this.fieldTermFrequencies),\n fieldRefsLength = fieldRefs.length,\n termIdfCache = Object.create(null)\n\n for (var i = 0; i < fieldRefsLength; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n fieldName = fieldRef.fieldName,\n fieldLength = this.fieldLengths[fieldRef],\n fieldVector = new lunr.Vector,\n termFrequencies = this.fieldTermFrequencies[fieldRef],\n terms = Object.keys(termFrequencies),\n termsLength = terms.length\n\n\n var fieldBoost = this._fields[fieldName].boost || 1,\n docBoost = this._documents[fieldRef.docRef].boost || 1\n\n for (var j = 0; j < termsLength; j++) {\n var term = terms[j],\n tf = termFrequencies[term],\n termIndex = this.invertedIndex[term]._index,\n idf, score, scoreWithPrecision\n\n if (termIdfCache[term] === undefined) {\n idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n termIdfCache[term] = idf\n } else {\n idf = termIdfCache[term]\n }\n\n score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n score *= fieldBoost\n score *= docBoost\n scoreWithPrecision = Math.round(score * 1000) / 1000\n // Converts 1.23456789 to 1.234.\n // Reducing the precision so that the vectors take up less\n // space when serialised. Doing it now so that they behave\n // the same before and after serialisation. Also, this is\n // the fastest approach to reducing a number's precision in\n // JavaScript.\n\n fieldVector.insert(termIndex, scoreWithPrecision)\n }\n\n fieldVectors[fieldRef] = fieldVector\n }\n\n this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n this.tokenSet = lunr.TokenSet.fromArray(\n Object.keys(this.invertedIndex).sort()\n )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n this.calculateAverageFieldLengths()\n this.createFieldVectors()\n this.createTokenSet()\n\n return new lunr.Index({\n invertedIndex: this.invertedIndex,\n fieldVectors: this.fieldVectors,\n tokenSet: this.tokenSet,\n fields: Object.keys(this._fields),\n pipeline: this.searchPipeline\n })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n var args = Array.prototype.slice.call(arguments, 1)\n args.unshift(this)\n fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n var clonedMetadata = Object.create(null),\n metadataKeys = Object.keys(metadata || {})\n\n // Cloning the metadata to prevent the original\n // being mutated during match data combination.\n // Metadata is kept in an array within the inverted\n // index so cloning the data can be done with\n // Array#slice\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n clonedMetadata[key] = metadata[key].slice()\n }\n\n this.metadata = Object.create(null)\n\n if (term !== undefined) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = clonedMetadata\n }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n var terms = Object.keys(otherMatchData.metadata)\n\n for (var i = 0; i < terms.length; i++) {\n var term = terms[i],\n fields = Object.keys(otherMatchData.metadata[term])\n\n if (this.metadata[term] == undefined) {\n this.metadata[term] = Object.create(null)\n }\n\n for (var j = 0; j < fields.length; j++) {\n var field = fields[j],\n keys = Object.keys(otherMatchData.metadata[term][field])\n\n if (this.metadata[term][field] == undefined) {\n this.metadata[term][field] = Object.create(null)\n }\n\n for (var k = 0; k < keys.length; k++) {\n var key = keys[k]\n\n if (this.metadata[term][field][key] == undefined) {\n this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n } else {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n }\n\n }\n }\n }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n if (!(term in this.metadata)) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = metadata\n return\n }\n\n if (!(field in this.metadata[term])) {\n this.metadata[term][field] = metadata\n return\n }\n\n var metadataKeys = Object.keys(metadata)\n\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n\n if (key in this.metadata[term][field]) {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n } else {\n this.metadata[term][field][key] = metadata[key]\n }\n }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n this.clauses = []\n this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with trailing wildcard \n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example query term with leading and trailing wildcard \n * query.term('foo', {\n * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with required presence \n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n /**\n * Term's presence in a document is optional, this is the default value.\n */\n OPTIONAL: 1,\n\n /**\n * Term's presence in a document is required, documents that do not contain\n * this term will not be returned.\n */\n REQUIRED: 2,\n\n /**\n * Term's presence in a document is prohibited, documents that do contain\n * this term will not be returned.\n */\n PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n if (!('fields' in clause)) {\n clause.fields = this.allFields\n }\n\n if (!('boost' in clause)) {\n clause.boost = 1\n }\n\n if (!('usePipeline' in clause)) {\n clause.usePipeline = true\n }\n\n if (!('wildcard' in clause)) {\n clause.wildcard = lunr.Query.wildcard.NONE\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n clause.term = \"*\" + clause.term\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n clause.term = \"\" + clause.term + \"*\"\n }\n\n if (!('presence' in clause)) {\n clause.presence = lunr.Query.presence.OPTIONAL\n }\n\n this.clauses.push(clause)\n\n return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n for (var i = 0; i < this.clauses.length; i++) {\n if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example adding a single term to a query \n * query.term(\"foo\")\n * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard \n * query.term(\"foo\", {\n * fields: [\"title\"],\n * boost: 10,\n * wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example using lunr.tokenizer to convert a string to tokens before using them as terms \n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n if (Array.isArray(term)) {\n term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n return this\n }\n\n var clause = options || {}\n clause.term = term.toString()\n\n this.clause(clause)\n\n return this\n}\nlunr.QueryParseError = function (message, start, end) {\n this.name = \"QueryParseError\"\n this.message = message\n this.start = start\n this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n this.lexemes = []\n this.str = str\n this.length = str.length\n this.pos = 0\n this.start = 0\n this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n var state = lunr.QueryLexer.lexText\n\n while (state) {\n state = state(this)\n }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n var subSlices = [],\n sliceStart = this.start,\n sliceEnd = this.pos\n\n for (var i = 0; i < this.escapeCharPositions.length; i++) {\n sliceEnd = this.escapeCharPositions[i]\n subSlices.push(this.str.slice(sliceStart, sliceEnd))\n sliceStart = sliceEnd + 1\n }\n\n subSlices.push(this.str.slice(sliceStart, this.pos))\n this.escapeCharPositions.length = 0\n\n return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n this.lexemes.push({\n type: type,\n str: this.sliceString(),\n start: this.start,\n end: this.pos\n })\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n this.escapeCharPositions.push(this.pos - 1)\n this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n if (this.pos >= this.length) {\n return lunr.QueryLexer.EOS\n }\n\n var char = this.str.charAt(this.pos)\n this.pos += 1\n return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n if (this.start == this.pos) {\n this.pos += 1\n }\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n var char, charCode\n\n do {\n char = this.next()\n charCode = char.charCodeAt(0)\n } while (charCode > 47 && charCode < 58)\n\n if (char != lunr.QueryLexer.EOS) {\n this.backup()\n }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.FIELD)\n lexer.ignore()\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n if (lexer.width() > 1) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.TERM)\n }\n\n lexer.ignore()\n\n if (lexer.more()) {\n return lunr.QueryLexer.lexText\n }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.BOOST)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n while (true) {\n var char = lexer.next()\n\n if (char == lunr.QueryLexer.EOS) {\n return lunr.QueryLexer.lexEOS\n }\n\n // Escape character is '\\'\n if (char.charCodeAt(0) == 92) {\n lexer.escapeCharacter()\n continue\n }\n\n if (char == \":\") {\n return lunr.QueryLexer.lexField\n }\n\n if (char == \"~\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexEditDistance\n }\n\n if (char == \"^\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexBoost\n }\n\n // \"+\" indicates term presence is required\n // checking for length to ensure that only\n // leading \"+\" are considered\n if (char == \"+\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n // \"-\" indicates term presence is prohibited\n // checking for length to ensure that only\n // leading \"-\" are considered\n if (char == \"-\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n if (char.match(lunr.QueryLexer.termSeparator)) {\n return lunr.QueryLexer.lexTerm\n }\n }\n}\n\nlunr.QueryParser = function (str, query) {\n this.lexer = new lunr.QueryLexer (str)\n this.query = query\n this.currentClause = {}\n this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n this.lexer.run()\n this.lexemes = this.lexer.lexemes\n\n var state = lunr.QueryParser.parseClause\n\n while (state) {\n state = state(this)\n }\n\n return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n var lexeme = this.peekLexeme()\n this.lexemeIdx += 1\n return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n var completedClause = this.currentClause\n this.query.clause(completedClause)\n this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n var lexeme = parser.peekLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.type) {\n case lunr.QueryLexer.PRESENCE:\n return lunr.QueryParser.parsePresence\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n if (lexeme.str.length >= 1) {\n errorMessage += \" with value '\" + lexeme.str + \"'\"\n }\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.str) {\n case \"-\":\n parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n break\n case \"+\":\n parser.currentClause.presence = lunr.Query.presence.REQUIRED\n break\n default:\n var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term or field, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.fields = [lexeme.str]\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n parser.currentClause.term = lexeme.str.toLowerCase()\n\n if (lexeme.str.indexOf(\"*\") != -1) {\n parser.currentClause.usePipeline = false\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var editDistance = parseInt(lexeme.str, 10)\n\n if (isNaN(editDistance)) {\n var errorMessage = \"edit distance must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.editDistance = editDistance\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var boost = parseInt(lexeme.str, 10)\n\n if (isNaN(boost)) {\n var errorMessage = \"boost must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.boost = boost\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\n /**\n * export the module via AMD, CommonJS or as a browser global\n * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n */\n ;(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define(factory)\n } else if (typeof exports === 'object') {\n /**\n * Node. Does not work with strict CommonJS, but\n * only CommonJS-like enviroments that support module.exports,\n * like Node.\n */\n module.exports = factory()\n } else {\n // Browser globals (root is window)\n root.lunr = factory()\n }\n }(this, function () {\n /**\n * Just return a value to define the module export.\n * This example returns an object, but the module\n * can return a function as the exported value.\n */\n return lunr\n }))\n})();\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport { getElement } from \"~/browser/element/_\"\nimport \"~/polyfills\"\n\nimport { Search } from \"../../_\"\nimport { SearchConfig } from \"../../config\"\nimport {\n SearchMessage,\n SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for `iframe-worker` shim\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous shim implementation. The cool\n * thing is that awaiting a non-Promise will convert it into a Promise, so\n * extending the type definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n *\n * @param urls - Scripts to load\n *\n * @returns Promise resolving with no result\n */\ndeclare global {\n function importScripts(...urls: string[]): Promise | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages which are defined as part of the search configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n config: SearchConfig\n): Promise {\n let base = \"../lunr\"\n\n /* Detect `iframe-worker` and fix base URL */\n if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n const worker = getElement(\"script[src]\")\n const [path] = worker.src.split(\"/worker\")\n\n /* Prefix base with path */\n base = base.replace(\"..\", path)\n }\n\n /* Add scripts for languages */\n const scripts = []\n for (const lang of config.lang) {\n switch (lang) {\n\n /* Add segmenter for Japanese */\n case \"ja\":\n scripts.push(`${base}/tinyseg.js`)\n break\n\n /* Add segmenter for Hindi and Thai */\n case \"hi\":\n case \"th\":\n scripts.push(`${base}/wordcut.js`)\n break\n }\n\n /* Add language support */\n if (lang !== \"en\")\n scripts.push(`${base}/min/lunr.${lang}.min.js`)\n }\n\n /* Add multi-language support */\n if (config.lang.length > 1)\n scripts.push(`${base}/min/lunr.multi.min.js`)\n\n /* Load scripts synchronously */\n if (scripts.length)\n await importScripts(\n `${base}/min/lunr.stemmer.support.min.js`,\n ...scripts\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n message: SearchMessage\n): Promise {\n switch (message.type) {\n\n /* Search setup message */\n case SearchMessageType.SETUP:\n await setupSearchLanguages(message.data.config)\n index = new Search(message.data)\n return {\n type: SearchMessageType.READY\n }\n\n /* Search query message */\n case SearchMessageType.QUERY:\n const query = message.data\n try {\n return {\n type: SearchMessageType.RESULT,\n data: index.search(query)\n }\n\n /* Return empty result in case of error */\n } catch (err) {\n console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n console.warn(err)\n return {\n type: SearchMessageType.RESULT,\n data: { items: [] }\n }\n }\n\n /* All other messages */\n default:\n throw new TypeError(\"Invalid message type\")\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* Expose Lunr.js in global scope, or stemmers won't work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements(\n selector: string, node?: ParentNode\n): T[]\n\nexport function getElements(\n selector: string, node: ParentNode = document\n): T[] {\n return Array.from(node.querySelectorAll(selector))\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * Note that this function assumes that the element is present. If unsure if an\n * element is existent, use the `getOptionalElement` function instead.\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement(\n selector: string, node?: ParentNode\n): T\n\nexport function getElement(\n selector: string, node: ParentNode = document\n): T {\n const el = getOptionalElement(selector, node)\n if (typeof el === \"undefined\")\n throw new ReferenceError(\n `Missing element: expected \"${selector}\" to be present`\n )\n\n /* Return element */\n return el\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an optional element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getOptionalElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T] | undefined\n\nexport function getOptionalElement(\n selector: string, node?: ParentNode\n): T | undefined\n\nexport function getOptionalElement(\n selector: string, node: ParentNode = document\n): T | undefined {\n return node.querySelector(selector) || undefined\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n return (\n document.activeElement?.shadowRoot?.activeElement as HTMLElement ??\n document.activeElement as HTMLElement ??\n undefined\n )\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Polyfills\n * ------------------------------------------------------------------------- */\n\n/* Polyfill `Object.entries` */\nif (!Object.entries)\n Object.entries = function (obj: object) {\n const data: [string, string][] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push([key, obj[key]])\n\n /* Return entries */\n return data\n }\n\n/* Polyfill `Object.values` */\nif (!Object.values)\n Object.values = function (obj: object) {\n const data: string[] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push(obj[key])\n\n /* Return values */\n return data\n }\n\n/* ------------------------------------------------------------------------- */\n\n/* Polyfills for `Element` */\nif (typeof Element !== \"undefined\") {\n\n /* Polyfill `Element.scrollTo` */\n if (!Element.prototype.scrollTo)\n Element.prototype.scrollTo = function (\n x?: ScrollToOptions | number, y?: number\n ): void {\n if (typeof x === \"object\") {\n this.scrollLeft = x.left!\n this.scrollTop = x.top!\n } else {\n this.scrollLeft = x!\n this.scrollTop = y!\n }\n }\n\n /* Polyfill `Element.replaceWith` */\n if (!Element.prototype.replaceWith)\n Element.prototype.replaceWith = function (\n ...nodes: Array\n ): void {\n const parent = this.parentNode\n if (parent) {\n if (nodes.length === 0)\n parent.removeChild(this)\n\n /* Replace children and create text nodes */\n for (let i = nodes.length - 1; i >= 0; i--) {\n let node = nodes[i]\n if (typeof node === \"string\")\n node = document.createTextNode(node)\n else if (node.parentNode)\n node.parentNode.removeChild(node)\n\n /* Replace child or insert before previous sibling */\n if (!i)\n parent.replaceChild(node, this)\n else\n parent.insertBefore(this.previousSibling!, node)\n }\n }\n }\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search configuration\n */\nexport interface SearchConfig {\n lang: string[] /* Search languages */\n separator: string /* Search separator */\n pipeline: SearchPipelineFn[] /* Search pipeline */\n}\n\n/**\n * Search document\n */\nexport interface SearchDocument {\n location: string /* Document location */\n title: string /* Document title */\n text: string /* Document text */\n tags?: string[] /* Document tags */\n boost?: number /* Document boost */\n parent?: SearchDocument /* Document parent */\n}\n\n/**\n * Search options\n */\nexport interface SearchOptions {\n suggest: boolean /* Search suggestions */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport interface SearchIndex {\n config: SearchConfig /* Search configuration */\n docs: SearchDocument[] /* Search documents */\n options: SearchOptions /* Search options */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search pipeline function\n */\ntype SearchPipelineFn =\n | \"trimmer\" /* Trimmer */\n | \"stopWordFilter\" /* Stop word filter */\n | \"stemmer\" /* Stemmer */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document map\n *\n * This function creates a mapping of URLs (including anchors) to the actual\n * articles and sections. It relies on the invariant that the search index is\n * ordered with the main article appearing before all sections with anchors.\n * If this is not the case, the logic music be changed.\n *\n * @param docs - Search documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchDocument[]\n): Map {\n const map = new Map()\n for (const doc of docs) {\n const [path] = doc.location.split(\"#\")\n\n /* Add document article */\n const article = map.get(path)\n if (typeof article === \"undefined\") {\n map.set(path, doc)\n\n /* Add document section */\n } else {\n map.set(doc.location, doc)\n doc.parent = article\n }\n }\n\n /* Return search document map */\n return map\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Visitor function\n *\n * @param start - Start offset\n * @param end - End offset\n */\ntype VisitorFn = (\n start: number, end: number\n) => void\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Split a string using the given separator\n *\n * @param input - Input value\n * @param separator - Separator\n * @param fn - Visitor function\n */\nexport function split(\n input: string, separator: RegExp, fn: VisitorFn\n): void {\n separator = new RegExp(separator, \"g\")\n\n /* Split string using separator */\n let match: RegExpExecArray | null\n let index = 0\n do {\n match = separator.exec(input)\n\n /* Emit non-empty range */\n const until = match?.index ?? input.length\n if (index < until)\n fn(index, until)\n\n /* Update last index */\n if (match) {\n const [term] = match\n index = match.index + term.length\n\n /* Support zero-length lookaheads */\n if (term.length === 0)\n separator.lastIndex = match.index + 1\n }\n } while (match)\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Extraction type\n *\n * This type defines the possible values that are encoded into the first two\n * bits of a section that is part of the blocks of a tokenization table. There\n * are three types of interest: HTML opening and closing tags, as well as the\n * actual text content we need to extract for indexing.\n */\nexport const enum Extract {\n TAG_OPEN = 0, /* HTML opening tag */\n TEXT = 1, /* Text content */\n TAG_CLOSE = 2 /* HTML closing tag */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Visitor function\n *\n * @param block - Block index\n * @param type - Extraction type\n * @param start - Start offset\n * @param end - End offset\n */\ntype VisitorFn = (\n block: number, type: Extract, start: number, end: number\n) => void\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Split a string into markup and text sections\n *\n * This function scans a string and divides it up into sections of markup and\n * text. For each section, it invokes the given visitor function with the block\n * index, extraction type, as well as start and end offsets. Using a visitor\n * function (= streaming data) is ideal for minimizing pressure on the GC.\n *\n * @param input - Input value\n * @param fn - Visitor function\n */\nexport function extract(\n input: string, fn: VisitorFn\n): void {\n\n let block = 0 /* Current block */\n let start = 0 /* Current start offset */\n let end = 0 /* Current end offset */\n\n /* Split string into sections */\n for (let stack = 0; end < input.length; end++) {\n\n /* Opening tag after non-empty section */\n if (input.charAt(end) === \"<\" && end > start) {\n fn(block, Extract.TEXT, start, start = end)\n\n /* Closing tag */\n } else if (input.charAt(end) === \">\") {\n if (input.charAt(start + 1) === \"/\") {\n if (--stack === 0)\n fn(block++, Extract.TAG_CLOSE, start, end + 1)\n\n /* Tag is not self-closing */\n } else if (input.charAt(end - 1) !== \"/\") {\n if (stack++ === 0)\n fn(block, Extract.TAG_OPEN, start, end + 1)\n }\n\n /* New section */\n start = end + 1\n }\n }\n\n /* Add trailing section */\n if (end > start)\n fn(block, Extract.TEXT, start, end)\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Position table\n */\nexport type PositionTable = number[][]\n\n/**\n * Position\n */\nexport type Position = number\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Highlight all occurrences in a string\n *\n * This function receives a field's value (e.g. like `title` or `text`), it's\n * position table that was generated during indexing, and the positions found\n * when executing the query. It then highlights all occurrences, and returns\n * their concatenation. In case of multiple blocks, two are returned.\n *\n * @param input - Input value\n * @param table - Table for indexing\n * @param positions - Occurrences\n * @param full - Full results\n *\n * @returns Highlighted string value\n */\nexport function highlight(\n input: string, table: PositionTable, positions: Position[], full = false\n): string {\n return highlightAll([input], table, positions, full).pop()!\n}\n\n/**\n * Highlight all occurrences in a set of strings\n *\n * @param inputs - Input values\n * @param table - Table for indexing\n * @param positions - Occurrences\n * @param full - Full results\n *\n * @returns Highlighted string values\n */\nexport function highlightAll(\n inputs: string[], table: PositionTable, positions: Position[], full = false\n): string[] {\n\n /* Map blocks to input values */\n const mapping = [0]\n for (let t = 1; t < table.length; t++) {\n const prev = table[t - 1]\n const next = table[t]\n\n /* Check if table points to new block */\n const p = prev[prev.length - 1] >>> 2 & 0x3FF\n const q = next[0] >>> 12\n\n /* Add block to mapping */\n mapping.push(+(p > q) + mapping[mapping.length - 1])\n }\n\n /* Highlight strings one after another */\n return inputs.map((input, i) => {\n let cursor = 0\n\n /* Map occurrences to blocks */\n const blocks = new Map()\n for (const p of positions.sort((a, b) => a - b)) {\n const index = p & 0xFFFFF\n const block = p >>> 20\n if (mapping[block] !== i)\n continue\n\n /* Ensure presence of block group */\n let group = blocks.get(block)\n if (typeof group === \"undefined\")\n blocks.set(block, group = [])\n\n /* Add index to group */\n group.push(index)\n }\n\n /* Just return string, if no occurrences */\n if (blocks.size === 0)\n return input\n\n /* Compute slices */\n const slices: string[] = []\n for (const [block, indexes] of blocks) {\n const t = table[block]\n\n /* Extract positions and length */\n const start = t[0] >>> 12\n const end = t[t.length - 1] >>> 12\n const length = t[t.length - 1] >>> 2 & 0x3FF\n\n /* Add prefix, if full results are desired */\n if (full && start > cursor)\n slices.push(input.slice(cursor, start))\n\n /* Extract and highlight slice */\n let slice = input.slice(start, end + length)\n for (const j of indexes.sort((a, b) => b - a)) {\n\n /* Retrieve offset and length of match */\n const p = (t[j] >>> 12) - start\n const q = (t[j] >>> 2 & 0x3FF) + p\n\n /* Wrap occurrence */\n slice = [\n slice.slice(0, p),\n \"\",\n slice.slice(p, q),\n \"\",\n slice.slice(q)\n ].join(\"\")\n }\n\n /* Update cursor */\n cursor = end + length\n\n /* Append slice and abort if we have two */\n if (slices.push(slice) === 2)\n break\n }\n\n /* Add suffix, if full results are desired */\n if (full && cursor < input.length)\n slices.push(input.slice(cursor))\n\n /* Return highlighted slices */\n return slices.join(\"\")\n })\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { split } from \"../_\"\nimport {\n Extract,\n extract\n} from \"../extract\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Split a string or set of strings into tokens\n *\n * This tokenizer supersedes the default tokenizer that is provided by Lunr.js,\n * as it is aware of HTML tags and allows for multi-character splitting.\n *\n * It takes the given inputs, splits each of them into markup and text sections,\n * tokenizes and segments (if necessary) each of them, and then indexes them in\n * a table by using a compact bit representation. Bitwise techniques are used\n * to write and read from the table during indexing and querying.\n *\n * @see https://bit.ly/3W3Xw4J - Search: better, faster, smaller\n *\n * @param input - Input value(s)\n *\n * @returns Tokens\n */\nexport function tokenize(\n input?: string | string[]\n): lunr.Token[] {\n const tokens: lunr.Token[] = []\n if (typeof input === \"undefined\")\n return tokens\n\n /* Tokenize strings one after another */\n const inputs = Array.isArray(input) ? input : [input]\n for (let i = 0; i < inputs.length; i++) {\n const table = lunr.tokenizer.table\n const total = table.length\n\n /* Split string into sections and tokenize content blocks */\n extract(inputs[i], (block, type, start, end) => {\n table[block += total] ||= []\n switch (type) {\n\n /* Handle markup */\n case Extract.TAG_OPEN:\n case Extract.TAG_CLOSE:\n table[block].push(\n start << 12 |\n end - start << 2 |\n type\n )\n break\n\n /* Handle text content */\n case Extract.TEXT:\n const section = inputs[i].slice(start, end)\n split(section, lunr.tokenizer.separator, (index, until) => {\n\n /**\n * Apply segmenter after tokenization. Note that the segmenter will\n * also split words at word boundaries, which is not what we want,\n * so we need to check if we can somehow mitigate this behavior.\n */\n if (typeof lunr.segmenter !== \"undefined\") {\n const subsection = section.slice(index, until)\n if (/^[MHIK]$/.test(lunr.segmenter.ctype_(subsection))) {\n const segments = lunr.segmenter.segment(subsection)\n for (let s = 0, l = 0; s < segments.length; s++) {\n\n /* Add block to section */\n table[block] ||= []\n table[block].push(\n start + index + l << 12 |\n segments[s].length << 2 |\n type\n )\n\n /* Add token with position */\n tokens.push(new lunr.Token(\n segments[s].toLowerCase(), {\n position: block << 20 | table[block].length - 1\n }\n ))\n\n /* Keep track of length */\n l += segments[s].length\n }\n return\n }\n }\n\n /* Add block to section */\n table[block].push(\n start + index << 12 |\n until - index << 2 |\n type\n )\n\n /* Add token with position */\n tokens.push(new lunr.Token(\n section.slice(index, until).toLowerCase(), {\n position: block << 20 | table[block].length - 1\n }\n ))\n })\n }\n })\n }\n\n /* Return tokens */\n return tokens\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Visitor function\n *\n * @param value - String value\n *\n * @returns String term(s)\n */\ntype VisitorFn = (\n value: string\n) => string | string[]\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Trim excess whitespace from left and right.\n *\n * 2. Search for parts in quotation marks and prepend a `+` modifier to denote\n * that the resulting document must contain all parts, converting the query\n * to an `AND` query (as opposed to the default `OR` behavior). While users\n * may expect parts enclosed in quotation marks to map to span queries, i.e.\n * for which order is important, Lunr.js doesn't support them, so the best\n * we can do is to convert the parts to an `AND` query.\n *\n * 3. Replace control characters which are not located at the beginning of the\n * query or preceded by white space, or are not followed by a non-whitespace\n * character or are at the end of the query string. Furthermore, filter\n * unmatched quotation marks.\n *\n * 4. Split the query string at whitespace, then pass each part to the visitor\n * function for tokenization, and append a wildcard to every resulting term\n * that is not explicitly marked with a `+`, `-`, `~` or `^` modifier, since\n * it ensures consistent and stable ranking when multiple terms are entered.\n * Also, if a fuzzy or boost modifier are given, but no numeric value has\n * been entered, default to 1 to not induce a query error.\n *\n * @param query - Query value\n * @param fn - Visitor function\n *\n * @returns Transformed query value\n */\nexport function transform(\n query: string, fn: VisitorFn = term => term\n): string {\n return query\n\n /* => 1 */\n .trim()\n\n /* => 2 */\n .split(/\"([^\"]+)\"/g)\n .map((parts, index) => index & 1\n ? parts.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n : parts\n )\n .join(\"\")\n\n /* => 3 */\n .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\")\n\n /* => 4 */\n .split(/\\s+/g)\n .reduce((prev, term) => {\n const next = fn(term)\n return [...prev, ...Array.isArray(next) ? next : [next]]\n }, [] as string[])\n .map(term => /([~^]$)/.test(term) ? `${term}1` : term)\n .map(term => /(^[+-]|[~^]\\d+$)/.test(term) ? term : `${term}*`)\n .join(\" \")\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { split } from \"../../internal\"\nimport { transform } from \"../transform\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n presence: lunr.Query.presence /* Clause presence */\n term: string /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Transform search query\n *\n * This function lexes the given search query and applies the transformation\n * function to each term, preserving markup like `+` and `-` modifiers.\n *\n * @param query - Search query\n *\n * @returns Search query\n */\nexport function transformSearchQuery(\n query: string\n): string {\n\n /* Split query terms with tokenizer */\n return transform(query, part => {\n const terms: string[] = []\n\n /* Initialize lexer and analyze part */\n const lexer = new lunr.QueryLexer(part)\n lexer.run()\n\n /* Extract and tokenize term from lexeme */\n for (const { type, str: term, start, end } of lexer.lexemes)\n switch (type) {\n\n /* Hack: remove colon - see https://bit.ly/3wD3T3I */\n case \"FIELD\":\n if (![\"title\", \"text\", \"tags\"].includes(term))\n part = [\n part.slice(0, end),\n \" \",\n part.slice(end + 1)\n ].join(\"\")\n break\n\n /* Tokenize term */\n case \"TERM\":\n split(term, lunr.tokenizer.separator, (...range) => {\n terms.push([\n part.slice(0, start),\n term.slice(...range),\n part.slice(end)\n ].join(\"\"))\n })\n }\n\n /* Return terms */\n return terms\n })\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * Lunr.js itself has a bug where it doesn't detect or remove wildcards for\n * query clauses, so we must do this here.\n *\n * @see https://bit.ly/3DpTGtz - GitHub issue\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n value: string\n): SearchQueryClause[] {\n const query = new lunr.Query([\"title\", \"text\", \"tags\"])\n const parser = new lunr.QueryParser(value, query)\n\n /* Parse Search query */\n parser.parse()\n for (const clause of query.clauses) {\n clause.usePipeline = true\n\n /* Handle leading wildcard */\n if (clause.term.startsWith(\"*\")) {\n clause.wildcard = lunr.Query.wildcard.LEADING\n clause.term = clause.term.slice(1)\n }\n\n /* Handle trailing wildcard */\n if (clause.term.endsWith(\"*\")) {\n clause.wildcard = lunr.Query.wildcard.TRAILING\n clause.term = clause.term.slice(0, -1)\n }\n }\n\n /* Return query clauses */\n return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n const clauses = new Set(query)\n\n /* Match query clauses against terms */\n const result: SearchQueryTerms = {}\n for (let t = 0; t < terms.length; t++)\n for (const clause of clauses)\n if (terms[t].startsWith(clause.term)) {\n result[clause.term] = true\n clauses.delete(clause)\n }\n\n /* Annotate unmatched non-stopword query clauses */\n for (const clause of clauses)\n if (lunr.stopWordFilter?.(clause.term))\n result[clause.term] = false\n\n /* Return query terms */\n return result\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Segment a search query using the inverted index\n *\n * This function implements a clever approach to text segmentation for Asian\n * languages, as it used the information already available in the search index.\n * The idea is to greedily segment the search query based on the tokens that are\n * already part of the index, as described in the linked issue.\n *\n * @see https://bit.ly/3lwjrk7 - GitHub issue\n *\n * @param query - Query value\n * @param index - Inverted index\n *\n * @returns Segmented query value\n */\nexport function segment(\n query: string, index: object\n): Iterable {\n const segments = new Set()\n\n /* Segment search query */\n const wordcuts = new Uint16Array(query.length)\n for (let i = 0; i < query.length; i++)\n for (let j = i + 1; j < query.length; j++) {\n const value = query.slice(i, j)\n if (value in index)\n wordcuts[i] = j - i\n }\n\n /* Compute longest matches with minimum overlap */\n const stack = [0]\n for (let s = stack.length; s > 0;) {\n const p = stack[--s]\n for (let q = 1; q < wordcuts[p]; q++)\n if (wordcuts[p + q] > wordcuts[p] - q) {\n segments.add(query.slice(p, p + q))\n stack[s++] = p + q\n }\n\n /* Continue at end of query string */\n const q = p + wordcuts[p]\n if (wordcuts[q] && q < query.length - 1)\n stack[s++] = q\n\n /* Add current segment */\n segments.add(query.slice(p, q))\n }\n\n // @todo fix this case in the code block above, this is a hotfix\n if (segments.has(\"\"))\n return new Set([query])\n\n /* Return segmented query value */\n return segments\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n SearchDocument,\n SearchIndex,\n SearchOptions,\n setupSearchDocumentMap\n} from \"../config\"\nimport {\n Position,\n PositionTable,\n highlight,\n highlightAll,\n tokenize\n} from \"../internal\"\nimport {\n SearchQueryTerms,\n getSearchQueryTerms,\n parseSearchQuery,\n segment,\n transformSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search item\n */\nexport interface SearchItem\n extends SearchDocument\n{\n score: number /* Score (relevance) */\n terms: SearchQueryTerms /* Search query terms */\n}\n\n/**\n * Search result\n */\nexport interface SearchResult {\n items: SearchItem[][] /* Search items */\n suggest?: string[] /* Search suggestions */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create field extractor factory\n *\n * @param table - Position table map\n *\n * @returns Extractor factory\n */\nfunction extractor(table: Map) {\n return (name: keyof SearchDocument) => {\n return (doc: SearchDocument) => {\n if (typeof doc[name] === \"undefined\")\n return undefined\n\n /* Compute identifier and initialize table */\n const id = [doc.location, name].join(\":\")\n table.set(id, lunr.tokenizer.table = [])\n\n /* Return field value */\n return doc[name]\n }\n }\n}\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n const [x, y] = [new Set(a), new Set(b)]\n return [\n ...new Set([...x].filter(value => !y.has(value)))\n ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n /**\n * Search document map\n */\n protected map: Map\n\n /**\n * Search options\n */\n protected options: SearchOptions\n\n /**\n * The underlying Lunr.js search index\n */\n protected index: lunr.Index\n\n /**\n * Internal position table map\n */\n protected table: Map\n\n /**\n * Create the search integration\n *\n * @param data - Search index\n */\n public constructor({ config, docs, options }: SearchIndex) {\n const field = extractor(this.table = new Map())\n\n /* Set up document map and options */\n this.map = setupSearchDocumentMap(docs)\n this.options = options\n\n /* Set up document index */\n this.index = lunr(function () {\n this.metadataWhitelist = [\"position\"]\n this.b(0)\n\n /* Set up (multi-)language support */\n if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n // @ts-expect-error - namespace indexing not supported\n this.use(lunr[config.lang[0]])\n } else if (config.lang.length > 1) {\n this.use(lunr.multiLanguage(...config.lang))\n }\n\n /* Set up custom tokenizer (must be after language setup) */\n this.tokenizer = tokenize as typeof lunr.tokenizer\n lunr.tokenizer.separator = new RegExp(config.separator)\n\n /* Set up custom segmenter, if loaded */\n lunr.segmenter = \"TinySegmenter\" in lunr\n ? new lunr.TinySegmenter()\n : undefined\n\n /* Compute functions to be removed from the pipeline */\n const fns = difference([\n \"trimmer\", \"stopWordFilter\", \"stemmer\"\n ], config.pipeline)\n\n /* Remove functions from the pipeline for registered languages */\n for (const lang of config.lang.map(language => (\n // @ts-expect-error - namespace indexing not supported\n language === \"en\" ? lunr : lunr[language]\n )))\n for (const fn of fns) {\n this.pipeline.remove(lang[fn])\n this.searchPipeline.remove(lang[fn])\n }\n\n /* Set up index reference */\n this.ref(\"location\")\n\n /* Set up index fields */\n this.field(\"title\", { boost: 1e3, extractor: field(\"title\") })\n this.field(\"text\", { boost: 1e0, extractor: field(\"text\") })\n this.field(\"tags\", { boost: 1e6, extractor: field(\"tags\") })\n\n /* Add documents to index */\n for (const doc of docs)\n this.add(doc, { boost: doc.boost })\n })\n }\n\n /**\n * Search for matching documents\n *\n * @param query - Search query\n *\n * @returns Search result\n */\n public search(query: string): SearchResult {\n\n // Experimental Chinese segmentation\n query = query.replace(/\\p{sc=Han}+/gu, value => {\n return [...segment(value, this.index.invertedIndex)]\n .join(\"* \")\n })\n\n // @todo: move segmenter (above) into transformSearchQuery\n query = transformSearchQuery(query)\n if (!query)\n return { items: [] }\n\n /* Parse query to extract clauses for analysis */\n const clauses = parseSearchQuery(query)\n .filter(clause => (\n clause.presence !== lunr.Query.presence.PROHIBITED\n ))\n\n /* Perform search and post-process results */\n const groups = this.index.search(query)\n\n /* Apply post-query boosts based on title and search query terms */\n .reduce((item, { ref, score, matchData }) => {\n let doc = this.map.get(ref)\n if (typeof doc !== \"undefined\") {\n\n /* Shallow copy document */\n doc = { ...doc }\n if (doc.tags)\n doc.tags = [...doc.tags]\n\n /* Compute and analyze search query terms */\n const terms = getSearchQueryTerms(\n clauses,\n Object.keys(matchData.metadata)\n )\n\n /* Highlight matches in fields */\n for (const field of this.index.fields) {\n if (typeof doc[field] === \"undefined\")\n continue\n\n /* Collect positions from matches */\n const positions: Position[] = []\n for (const match of Object.values(matchData.metadata))\n if (typeof match[field] !== \"undefined\")\n positions.push(...match[field].position)\n\n /* Skip highlighting, if no positions were collected */\n if (!positions.length)\n continue\n\n /* Load table and determine highlighting method */\n const table = this.table.get([doc.location, field].join(\":\"))!\n const fn = Array.isArray(doc[field])\n ? highlightAll\n : highlight\n\n // @ts-expect-error - stop moaning, TypeScript!\n doc[field] = fn(doc[field], table, positions, field !== \"text\")\n }\n\n /* Highlight title and text and apply post-query boosts */\n const boost = +!doc.parent +\n Object.values(terms)\n .filter(t => t).length /\n Object.keys(terms).length\n\n /* Append item */\n item.push({\n ...doc,\n score: score * (1 + boost ** 2),\n terms\n })\n }\n return item\n }, [])\n\n /* Sort search results again after applying boosts */\n .sort((a, b) => b.score - a.score)\n\n /* Group search results by article */\n .reduce((items, result) => {\n const doc = this.map.get(result.location)\n if (typeof doc !== \"undefined\") {\n const ref = doc.parent\n ? doc.parent.location\n : doc.location\n items.set(ref, [...items.get(ref) || [], result])\n }\n return items\n }, new Map())\n\n /* Ensure that every item set has an article */\n for (const [ref, items] of groups)\n if (!items.find(item => item.location === ref)) {\n const doc = this.map.get(ref)!\n items.push({ ...doc, score: 0, terms: {} })\n }\n\n /* Generate search suggestions, if desired */\n let suggest: string[] | undefined\n if (this.options.suggest) {\n const titles = this.index.query(builder => {\n for (const clause of clauses)\n builder.term(clause.term, {\n fields: [\"title\"],\n presence: lunr.Query.presence.REQUIRED,\n wildcard: lunr.Query.wildcard.TRAILING\n })\n })\n\n /* Retrieve suggestions for best match */\n suggest = titles.length\n ? Object.keys(titles[0].matchData.metadata)\n : []\n }\n\n /* Return search result */\n return {\n items: [...groups.values()],\n ...typeof suggest !== \"undefined\" && { suggest }\n }\n }\n}\n"],
- "mappings": "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",
+ "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n * this.field('title')\n * this.field('body')\n * this.ref('id')\n *\n * documents.forEach(function (doc) {\n * this.add(doc)\n * }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n var builder = new lunr.Builder\n\n builder.pipeline.add(\n lunr.trimmer,\n lunr.stopWordFilter,\n lunr.stemmer\n )\n\n builder.searchPipeline.add(\n lunr.stemmer\n )\n\n config.call(builder, builder)\n return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n /* eslint-disable no-console */\n return function (message) {\n if (global.console && console.warn) {\n console.warn(message)\n }\n }\n /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n if (obj === void 0 || obj === null) {\n return \"\"\n } else {\n return obj.toString()\n }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n var clone = Object.create(null),\n keys = Object.keys(obj)\n\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i],\n val = obj[key]\n\n if (Array.isArray(val)) {\n clone[key] = val.slice()\n continue\n }\n\n if (typeof val === 'string' ||\n typeof val === 'number' ||\n typeof val === 'boolean') {\n clone[key] = val\n continue\n }\n\n throw new TypeError(\"clone is not deep and does not support nested objects\")\n }\n\n return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n this.docRef = docRef\n this.fieldName = fieldName\n this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n var n = s.indexOf(lunr.FieldRef.joiner)\n\n if (n === -1) {\n throw \"malformed field ref string\"\n }\n\n var fieldRef = s.slice(0, n),\n docRef = s.slice(n + 1)\n\n return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n if (this._stringValue == undefined) {\n this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n }\n\n return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n this.elements = Object.create(null)\n\n if (elements) {\n this.length = elements.length\n\n for (var i = 0; i < this.length; i++) {\n this.elements[elements[i]] = true\n }\n } else {\n this.length = 0\n }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n intersect: function (other) {\n return other\n },\n\n union: function () {\n return this\n },\n\n contains: function () {\n return true\n }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n intersect: function () {\n return this\n },\n\n union: function (other) {\n return other\n },\n\n contains: function () {\n return false\n }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n var a, b, elements, intersection = []\n\n if (other === lunr.Set.complete) {\n return this\n }\n\n if (other === lunr.Set.empty) {\n return other\n }\n\n if (this.length < other.length) {\n a = this\n b = other\n } else {\n a = other\n b = this\n }\n\n elements = Object.keys(a.elements)\n\n for (var i = 0; i < elements.length; i++) {\n var element = elements[i]\n if (element in b.elements) {\n intersection.push(element)\n }\n }\n\n return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n if (other === lunr.Set.complete) {\n return lunr.Set.complete\n }\n\n if (other === lunr.Set.empty) {\n return this\n }\n\n return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n var documentsWithTerm = 0\n\n for (var fieldName in posting) {\n if (fieldName == '_index') continue // Ignore the term index, its not a field\n documentsWithTerm += Object.keys(posting[fieldName]).length\n }\n\n var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n this.str = str || \"\"\n this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n * return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n this.str = fn(this.str, this.metadata)\n return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n fn = fn || function (s) { return s }\n return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n if (obj == null || obj == undefined) {\n return []\n }\n\n if (Array.isArray(obj)) {\n return obj.map(function (t) {\n return new lunr.Token(\n lunr.utils.asString(t).toLowerCase(),\n lunr.utils.clone(metadata)\n )\n })\n }\n\n var str = obj.toString().toLowerCase(),\n len = str.length,\n tokens = []\n\n for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n var char = str.charAt(sliceEnd),\n sliceLength = sliceEnd - sliceStart\n\n if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n if (sliceLength > 0) {\n var tokenMetadata = lunr.utils.clone(metadata) || {}\n tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n tokenMetadata[\"index\"] = tokens.length\n\n tokens.push(\n new lunr.Token (\n str.slice(sliceStart, sliceEnd),\n tokenMetadata\n )\n )\n }\n\n sliceStart = sliceEnd + 1\n }\n\n }\n\n return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n if (label in this.registeredFunctions) {\n lunr.utils.warn('Overwriting existing registered function: ' + label)\n }\n\n fn.label = label\n lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n if (!isRegistered) {\n lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n var pipeline = new lunr.Pipeline\n\n serialised.forEach(function (fnName) {\n var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n if (fn) {\n pipeline.add(fn)\n } else {\n throw new Error('Cannot load unregistered function: ' + fnName)\n }\n })\n\n return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n var fns = Array.prototype.slice.call(arguments)\n\n fns.forEach(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n this._stack.push(fn)\n }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n pos = pos + 1\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n var pos = this._stack.indexOf(fn)\n if (pos == -1) {\n return\n }\n\n this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n var stackLength = this._stack.length\n\n for (var i = 0; i < stackLength; i++) {\n var fn = this._stack[i]\n var memo = []\n\n for (var j = 0; j < tokens.length; j++) {\n var result = fn(tokens[j], j, tokens)\n\n if (result === null || result === void 0 || result === '') continue\n\n if (Array.isArray(result)) {\n for (var k = 0; k < result.length; k++) {\n memo.push(result[k])\n }\n } else {\n memo.push(result)\n }\n }\n\n tokens = memo\n }\n\n return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n var token = new lunr.Token (str, metadata)\n\n return this.run([token]).map(function (t) {\n return t.toString()\n })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n return this._stack.map(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n return fn.label\n })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n this._magnitude = 0\n this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n // For an empty vector the tuple can be inserted at the beginning\n if (this.elements.length == 0) {\n return 0\n }\n\n var start = 0,\n end = this.elements.length / 2,\n sliceLength = end - start,\n pivotPoint = Math.floor(sliceLength / 2),\n pivotIndex = this.elements[pivotPoint * 2]\n\n while (sliceLength > 1) {\n if (pivotIndex < index) {\n start = pivotPoint\n }\n\n if (pivotIndex > index) {\n end = pivotPoint\n }\n\n if (pivotIndex == index) {\n break\n }\n\n sliceLength = end - start\n pivotPoint = start + Math.floor(sliceLength / 2)\n pivotIndex = this.elements[pivotPoint * 2]\n }\n\n if (pivotIndex == index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex > index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex < index) {\n return (pivotPoint + 1) * 2\n }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n this.upsert(insertIdx, val, function () {\n throw \"duplicate index\"\n })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n this._magnitude = 0\n var position = this.positionForIndex(insertIdx)\n\n if (this.elements[position] == insertIdx) {\n this.elements[position + 1] = fn(this.elements[position + 1], val)\n } else {\n this.elements.splice(position, 0, insertIdx, val)\n }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n if (this._magnitude) return this._magnitude\n\n var sumOfSquares = 0,\n elementsLength = this.elements.length\n\n for (var i = 1; i < elementsLength; i += 2) {\n var val = this.elements[i]\n sumOfSquares += val * val\n }\n\n return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n var dotProduct = 0,\n a = this.elements, b = otherVector.elements,\n aLen = a.length, bLen = b.length,\n aVal = 0, bVal = 0,\n i = 0, j = 0\n\n while (i < aLen && j < bLen) {\n aVal = a[i], bVal = b[j]\n if (aVal < bVal) {\n i += 2\n } else if (aVal > bVal) {\n j += 2\n } else if (aVal == bVal) {\n dotProduct += a[i + 1] * b[j + 1]\n i += 2\n j += 2\n }\n }\n\n return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n var output = new Array (this.elements.length / 2)\n\n for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n output[j] = this.elements[i]\n }\n\n return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n var step2list = {\n \"ational\" : \"ate\",\n \"tional\" : \"tion\",\n \"enci\" : \"ence\",\n \"anci\" : \"ance\",\n \"izer\" : \"ize\",\n \"bli\" : \"ble\",\n \"alli\" : \"al\",\n \"entli\" : \"ent\",\n \"eli\" : \"e\",\n \"ousli\" : \"ous\",\n \"ization\" : \"ize\",\n \"ation\" : \"ate\",\n \"ator\" : \"ate\",\n \"alism\" : \"al\",\n \"iveness\" : \"ive\",\n \"fulness\" : \"ful\",\n \"ousness\" : \"ous\",\n \"aliti\" : \"al\",\n \"iviti\" : \"ive\",\n \"biliti\" : \"ble\",\n \"logi\" : \"log\"\n },\n\n step3list = {\n \"icate\" : \"ic\",\n \"ative\" : \"\",\n \"alize\" : \"al\",\n \"iciti\" : \"ic\",\n \"ical\" : \"ic\",\n \"ful\" : \"\",\n \"ness\" : \"\"\n },\n\n c = \"[^aeiou]\", // consonant\n v = \"[aeiouy]\", // vowel\n C = c + \"[^aeiouy]*\", // consonant sequence\n V = v + \"[aeiou]*\", // vowel sequence\n\n mgr0 = \"^(\" + C + \")?\" + V + C, // [C]VC... is m>0\n meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\", // [C]VC[V] is m=1\n mgr1 = \"^(\" + C + \")?\" + V + C + V + C, // [C]VCVC... is m>1\n s_v = \"^(\" + C + \")?\" + v; // vowel in stem\n\n var re_mgr0 = new RegExp(mgr0);\n var re_mgr1 = new RegExp(mgr1);\n var re_meq1 = new RegExp(meq1);\n var re_s_v = new RegExp(s_v);\n\n var re_1a = /^(.+?)(ss|i)es$/;\n var re2_1a = /^(.+?)([^s])s$/;\n var re_1b = /^(.+?)eed$/;\n var re2_1b = /^(.+?)(ed|ing)$/;\n var re_1b_2 = /.$/;\n var re2_1b_2 = /(at|bl|iz)$/;\n var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var re_1c = /^(.+?[^aeiou])y$/;\n var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n var re_5 = /^(.+?)e$/;\n var re_5_1 = /ll$/;\n var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var porterStemmer = function porterStemmer(w) {\n var stem,\n suffix,\n firstch,\n re,\n re2,\n re3,\n re4;\n\n if (w.length < 3) { return w; }\n\n firstch = w.substr(0,1);\n if (firstch == \"y\") {\n w = firstch.toUpperCase() + w.substr(1);\n }\n\n // Step 1a\n re = re_1a\n re2 = re2_1a;\n\n if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n // Step 1b\n re = re_1b;\n re2 = re2_1b;\n if (re.test(w)) {\n var fp = re.exec(w);\n re = re_mgr0;\n if (re.test(fp[1])) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1];\n re2 = re_s_v;\n if (re2.test(stem)) {\n w = stem;\n re2 = re2_1b_2;\n re3 = re3_1b_2;\n re4 = re4_1b_2;\n if (re2.test(w)) { w = w + \"e\"; }\n else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n else if (re4.test(w)) { w = w + \"e\"; }\n }\n }\n\n // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n re = re_1c;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n w = stem + \"i\";\n }\n\n // Step 2\n re = re_2;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step2list[suffix];\n }\n }\n\n // Step 3\n re = re_3;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step3list[suffix];\n }\n }\n\n // Step 4\n re = re_4;\n re2 = re2_4;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n if (re.test(stem)) {\n w = stem;\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1] + fp[2];\n re2 = re_mgr1;\n if (re2.test(stem)) {\n w = stem;\n }\n }\n\n // Step 5\n re = re_5;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n re2 = re_meq1;\n re3 = re3_5;\n if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n w = stem;\n }\n }\n\n re = re_5_1;\n re2 = re_mgr1;\n if (re.test(w) && re2.test(w)) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n\n // and turn initial Y back to y\n\n if (firstch == \"y\") {\n w = firstch.toLowerCase() + w.substr(1);\n }\n\n return w;\n };\n\n return function (token) {\n return token.update(porterStemmer);\n }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n var words = stopWords.reduce(function (memo, stopWord) {\n memo[stopWord] = stopWord\n return memo\n }, {})\n\n return function (token) {\n if (token && words[token.toString()] !== token.toString()) return token\n }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n 'a',\n 'able',\n 'about',\n 'across',\n 'after',\n 'all',\n 'almost',\n 'also',\n 'am',\n 'among',\n 'an',\n 'and',\n 'any',\n 'are',\n 'as',\n 'at',\n 'be',\n 'because',\n 'been',\n 'but',\n 'by',\n 'can',\n 'cannot',\n 'could',\n 'dear',\n 'did',\n 'do',\n 'does',\n 'either',\n 'else',\n 'ever',\n 'every',\n 'for',\n 'from',\n 'get',\n 'got',\n 'had',\n 'has',\n 'have',\n 'he',\n 'her',\n 'hers',\n 'him',\n 'his',\n 'how',\n 'however',\n 'i',\n 'if',\n 'in',\n 'into',\n 'is',\n 'it',\n 'its',\n 'just',\n 'least',\n 'let',\n 'like',\n 'likely',\n 'may',\n 'me',\n 'might',\n 'most',\n 'must',\n 'my',\n 'neither',\n 'no',\n 'nor',\n 'not',\n 'of',\n 'off',\n 'often',\n 'on',\n 'only',\n 'or',\n 'other',\n 'our',\n 'own',\n 'rather',\n 'said',\n 'say',\n 'says',\n 'she',\n 'should',\n 'since',\n 'so',\n 'some',\n 'than',\n 'that',\n 'the',\n 'their',\n 'them',\n 'then',\n 'there',\n 'these',\n 'they',\n 'this',\n 'tis',\n 'to',\n 'too',\n 'twas',\n 'us',\n 'wants',\n 'was',\n 'we',\n 'were',\n 'what',\n 'when',\n 'where',\n 'which',\n 'while',\n 'who',\n 'whom',\n 'why',\n 'will',\n 'with',\n 'would',\n 'yet',\n 'you',\n 'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n return token.update(function (s) {\n return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n this.final = false\n this.edges = {}\n this.id = lunr.TokenSet._nextId\n lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n var builder = new lunr.TokenSet.Builder\n\n for (var i = 0, len = arr.length; i < len; i++) {\n builder.insert(arr[i])\n }\n\n builder.finish()\n return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n if ('editDistance' in clause) {\n return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n } else {\n return lunr.TokenSet.fromString(clause.term)\n }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n var root = new lunr.TokenSet\n\n var stack = [{\n node: root,\n editsRemaining: editDistance,\n str: str\n }]\n\n while (stack.length) {\n var frame = stack.pop()\n\n // no edit\n if (frame.str.length > 0) {\n var char = frame.str.charAt(0),\n noEditNode\n\n if (char in frame.node.edges) {\n noEditNode = frame.node.edges[char]\n } else {\n noEditNode = new lunr.TokenSet\n frame.node.edges[char] = noEditNode\n }\n\n if (frame.str.length == 1) {\n noEditNode.final = true\n }\n\n stack.push({\n node: noEditNode,\n editsRemaining: frame.editsRemaining,\n str: frame.str.slice(1)\n })\n }\n\n if (frame.editsRemaining == 0) {\n continue\n }\n\n // insertion\n if (\"*\" in frame.node.edges) {\n var insertionNode = frame.node.edges[\"*\"]\n } else {\n var insertionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = insertionNode\n }\n\n if (frame.str.length == 0) {\n insertionNode.final = true\n }\n\n stack.push({\n node: insertionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str\n })\n\n // deletion\n // can only do a deletion if we have enough edits remaining\n // and if there are characters left to delete in the string\n if (frame.str.length > 1) {\n stack.push({\n node: frame.node,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // deletion\n // just removing the last character from the str\n if (frame.str.length == 1) {\n frame.node.final = true\n }\n\n // substitution\n // can only do a substitution if we have enough edits remaining\n // and if there are characters left to substitute\n if (frame.str.length >= 1) {\n if (\"*\" in frame.node.edges) {\n var substitutionNode = frame.node.edges[\"*\"]\n } else {\n var substitutionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = substitutionNode\n }\n\n if (frame.str.length == 1) {\n substitutionNode.final = true\n }\n\n stack.push({\n node: substitutionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // transposition\n // can only do a transposition if there are edits remaining\n // and there are enough characters to transpose\n if (frame.str.length > 1) {\n var charA = frame.str.charAt(0),\n charB = frame.str.charAt(1),\n transposeNode\n\n if (charB in frame.node.edges) {\n transposeNode = frame.node.edges[charB]\n } else {\n transposeNode = new lunr.TokenSet\n frame.node.edges[charB] = transposeNode\n }\n\n if (frame.str.length == 1) {\n transposeNode.final = true\n }\n\n stack.push({\n node: transposeNode,\n editsRemaining: frame.editsRemaining - 1,\n str: charA + frame.str.slice(2)\n })\n }\n }\n\n return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n var node = new lunr.TokenSet,\n root = node\n\n /*\n * Iterates through all characters within the passed string\n * appending a node for each character.\n *\n * When a wildcard character is found then a self\n * referencing edge is introduced to continually match\n * any number of any characters.\n */\n for (var i = 0, len = str.length; i < len; i++) {\n var char = str[i],\n final = (i == len - 1)\n\n if (char == \"*\") {\n node.edges[char] = node\n node.final = final\n\n } else {\n var next = new lunr.TokenSet\n next.final = final\n\n node.edges[char] = next\n node = next\n }\n }\n\n return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n var words = []\n\n var stack = [{\n prefix: \"\",\n node: this\n }]\n\n while (stack.length) {\n var frame = stack.pop(),\n edges = Object.keys(frame.node.edges),\n len = edges.length\n\n if (frame.node.final) {\n /* In Safari, at this point the prefix is sometimes corrupted, see:\n * https://github.com/olivernn/lunr.js/issues/279 Calling any\n * String.prototype method forces Safari to \"cast\" this string to what\n * it's supposed to be, fixing the bug. */\n frame.prefix.charAt(0)\n words.push(frame.prefix)\n }\n\n for (var i = 0; i < len; i++) {\n var edge = edges[i]\n\n stack.push({\n prefix: frame.prefix.concat(edge),\n node: frame.node.edges[edge]\n })\n }\n }\n\n return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n // NOTE: Using Object.keys here as this.edges is very likely\n // to enter 'hash-mode' with many keys being added\n //\n // avoiding a for-in loop here as it leads to the function\n // being de-optimised (at least in V8). From some simple\n // benchmarks the performance is comparable, but allowing\n // V8 to optimize may mean easy performance wins in the future.\n\n if (this._str) {\n return this._str\n }\n\n var str = this.final ? '1' : '0',\n labels = Object.keys(this.edges).sort(),\n len = labels.length\n\n for (var i = 0; i < len; i++) {\n var label = labels[i],\n node = this.edges[label]\n\n str = str + label + node.id\n }\n\n return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n var output = new lunr.TokenSet,\n frame = undefined\n\n var stack = [{\n qNode: b,\n output: output,\n node: this\n }]\n\n while (stack.length) {\n frame = stack.pop()\n\n // NOTE: As with the #toString method, we are using\n // Object.keys and a for loop instead of a for-in loop\n // as both of these objects enter 'hash' mode, causing\n // the function to be de-optimised in V8\n var qEdges = Object.keys(frame.qNode.edges),\n qLen = qEdges.length,\n nEdges = Object.keys(frame.node.edges),\n nLen = nEdges.length\n\n for (var q = 0; q < qLen; q++) {\n var qEdge = qEdges[q]\n\n for (var n = 0; n < nLen; n++) {\n var nEdge = nEdges[n]\n\n if (nEdge == qEdge || qEdge == '*') {\n var node = frame.node.edges[nEdge],\n qNode = frame.qNode.edges[qEdge],\n final = node.final && qNode.final,\n next = undefined\n\n if (nEdge in frame.output.edges) {\n // an edge already exists for this character\n // no need to create a new node, just set the finality\n // bit unless this node is already final\n next = frame.output.edges[nEdge]\n next.final = next.final || final\n\n } else {\n // no edge exists yet, must create one\n // set the finality bit and insert it\n // into the output\n next = new lunr.TokenSet\n next.final = final\n frame.output.edges[nEdge] = next\n }\n\n stack.push({\n qNode: qNode,\n output: next,\n node: node\n })\n }\n }\n }\n }\n\n return output\n}\nlunr.TokenSet.Builder = function () {\n this.previousWord = \"\"\n this.root = new lunr.TokenSet\n this.uncheckedNodes = []\n this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n var node,\n commonPrefix = 0\n\n if (word < this.previousWord) {\n throw new Error (\"Out of order word insertion\")\n }\n\n for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n if (word[i] != this.previousWord[i]) break\n commonPrefix++\n }\n\n this.minimize(commonPrefix)\n\n if (this.uncheckedNodes.length == 0) {\n node = this.root\n } else {\n node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n }\n\n for (var i = commonPrefix; i < word.length; i++) {\n var nextNode = new lunr.TokenSet,\n char = word[i]\n\n node.edges[char] = nextNode\n\n this.uncheckedNodes.push({\n parent: node,\n char: char,\n child: nextNode\n })\n\n node = nextNode\n }\n\n node.final = true\n this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n var node = this.uncheckedNodes[i],\n childKey = node.child.toString()\n\n if (childKey in this.minimizedNodes) {\n node.parent.edges[node.char] = this.minimizedNodes[childKey]\n } else {\n // Cache the key for this node since\n // we know it can't change anymore\n node.child._str = childKey\n\n this.minimizedNodes[childKey] = node.child\n }\n\n this.uncheckedNodes.pop()\n }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n this.invertedIndex = attrs.invertedIndex\n this.fieldVectors = attrs.fieldVectors\n this.tokenSet = attrs.tokenSet\n this.fields = attrs.fields\n this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example Simple single term query \n * hello\n * @example Multiple term query \n * hello world\n * @example term scoped to a field \n * title:hello\n * @example term with a boost of 10 \n * hello^10\n * @example term with an edit distance of 2 \n * hello~2\n * @example terms with presence modifiers \n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first. For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n return this.query(function (query) {\n var parser = new lunr.QueryParser(queryString, query)\n parser.parse()\n })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n // for each query clause\n // * process terms\n // * expand terms from token set\n // * find matching documents and metadata\n // * get document vectors\n // * score documents\n\n var query = new lunr.Query(this.fields),\n matchingFields = Object.create(null),\n queryVectors = Object.create(null),\n termFieldCache = Object.create(null),\n requiredMatches = Object.create(null),\n prohibitedMatches = Object.create(null)\n\n /*\n * To support field level boosts a query vector is created per\n * field. An empty vector is eagerly created to support negated\n * queries.\n */\n for (var i = 0; i < this.fields.length; i++) {\n queryVectors[this.fields[i]] = new lunr.Vector\n }\n\n fn.call(query, query)\n\n for (var i = 0; i < query.clauses.length; i++) {\n /*\n * Unless the pipeline has been disabled for this term, which is\n * the case for terms with wildcards, we need to pass the clause\n * term through the search pipeline. A pipeline returns an array\n * of processed terms. Pipeline functions may expand the passed\n * term, which means we may end up performing multiple index lookups\n * for a single query term.\n */\n var clause = query.clauses[i],\n terms = null,\n clauseMatches = lunr.Set.empty\n\n if (clause.usePipeline) {\n terms = this.pipeline.runString(clause.term, {\n fields: clause.fields\n })\n } else {\n terms = [clause.term]\n }\n\n for (var m = 0; m < terms.length; m++) {\n var term = terms[m]\n\n /*\n * Each term returned from the pipeline needs to use the same query\n * clause object, e.g. the same boost and or edit distance. The\n * simplest way to do this is to re-use the clause object but mutate\n * its term property.\n */\n clause.term = term\n\n /*\n * From the term in the clause we create a token set which will then\n * be used to intersect the indexes token set to get a list of terms\n * to lookup in the inverted index\n */\n var termTokenSet = lunr.TokenSet.fromClause(clause),\n expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n /*\n * If a term marked as required does not exist in the tokenSet it is\n * impossible for the search to return any matches. We set all the field\n * scoped required matches set to empty and stop examining any further\n * clauses.\n */\n if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = lunr.Set.empty\n }\n\n break\n }\n\n for (var j = 0; j < expandedTerms.length; j++) {\n /*\n * For each term get the posting and termIndex, this is required for\n * building the query vector.\n */\n var expandedTerm = expandedTerms[j],\n posting = this.invertedIndex[expandedTerm],\n termIndex = posting._index\n\n for (var k = 0; k < clause.fields.length; k++) {\n /*\n * For each field that this query term is scoped by (by default\n * all fields are in scope) we need to get all the document refs\n * that have this term in that field.\n *\n * The posting is the entry in the invertedIndex for the matching\n * term from above.\n */\n var field = clause.fields[k],\n fieldPosting = posting[field],\n matchingDocumentRefs = Object.keys(fieldPosting),\n termField = expandedTerm + \"/\" + field,\n matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n /*\n * if the presence of this term is required ensure that the matching\n * documents are added to the set of required matches for this clause.\n *\n */\n if (clause.presence == lunr.Query.presence.REQUIRED) {\n clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n if (requiredMatches[field] === undefined) {\n requiredMatches[field] = lunr.Set.complete\n }\n }\n\n /*\n * if the presence of this term is prohibited ensure that the matching\n * documents are added to the set of prohibited matches for this field,\n * creating that set if it does not yet exist.\n */\n if (clause.presence == lunr.Query.presence.PROHIBITED) {\n if (prohibitedMatches[field] === undefined) {\n prohibitedMatches[field] = lunr.Set.empty\n }\n\n prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n /*\n * Prohibited matches should not be part of the query vector used for\n * similarity scoring and no metadata should be extracted so we continue\n * to the next field\n */\n continue\n }\n\n /*\n * The query field vector is populated using the termIndex found for\n * the term and a unit value with the appropriate boost applied.\n * Using upsert because there could already be an entry in the vector\n * for the term we are working with. In that case we just add the scores\n * together.\n */\n queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n /**\n * If we've already seen this term, field combo then we've already collected\n * the matching documents and metadata, no need to go through all that again\n */\n if (termFieldCache[termField]) {\n continue\n }\n\n for (var l = 0; l < matchingDocumentRefs.length; l++) {\n /*\n * All metadata for this term/field/document triple\n * are then extracted and collected into an instance\n * of lunr.MatchData ready to be returned in the query\n * results\n */\n var matchingDocumentRef = matchingDocumentRefs[l],\n matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n metadata = fieldPosting[matchingDocumentRef],\n fieldMatch\n\n if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n } else {\n fieldMatch.add(expandedTerm, field, metadata)\n }\n\n }\n\n termFieldCache[termField] = true\n }\n }\n }\n\n /**\n * If the presence was required we need to update the requiredMatches field sets.\n * We do this after all fields for the term have collected their matches because\n * the clause terms presence is required in _any_ of the fields not _all_ of the\n * fields.\n */\n if (clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n }\n }\n }\n\n /**\n * Need to combine the field scoped required and prohibited\n * matching documents into a global set of required and prohibited\n * matches\n */\n var allRequiredMatches = lunr.Set.complete,\n allProhibitedMatches = lunr.Set.empty\n\n for (var i = 0; i < this.fields.length; i++) {\n var field = this.fields[i]\n\n if (requiredMatches[field]) {\n allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n }\n\n if (prohibitedMatches[field]) {\n allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n }\n }\n\n var matchingFieldRefs = Object.keys(matchingFields),\n results = [],\n matches = Object.create(null)\n\n /*\n * If the query is negated (contains only prohibited terms)\n * we need to get _all_ fieldRefs currently existing in the\n * index. This is only done when we know that the query is\n * entirely prohibited terms to avoid any cost of getting all\n * fieldRefs unnecessarily.\n *\n * Additionally, blank MatchData must be created to correctly\n * populate the results.\n */\n if (query.isNegated()) {\n matchingFieldRefs = Object.keys(this.fieldVectors)\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n var matchingFieldRef = matchingFieldRefs[i]\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n matchingFields[matchingFieldRef] = new lunr.MatchData\n }\n }\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n /*\n * Currently we have document fields that match the query, but we\n * need to return documents. The matchData and scores are combined\n * from multiple fields belonging to the same document.\n *\n * Scores are calculated by field, using the query vectors created\n * above, and combined into a final document score using addition.\n */\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n docRef = fieldRef.docRef\n\n if (!allRequiredMatches.contains(docRef)) {\n continue\n }\n\n if (allProhibitedMatches.contains(docRef)) {\n continue\n }\n\n var fieldVector = this.fieldVectors[fieldRef],\n score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n docMatch\n\n if ((docMatch = matches[docRef]) !== undefined) {\n docMatch.score += score\n docMatch.matchData.combine(matchingFields[fieldRef])\n } else {\n var match = {\n ref: docRef,\n score: score,\n matchData: matchingFields[fieldRef]\n }\n matches[docRef] = match\n results.push(match)\n }\n }\n\n /*\n * Sort the results objects by score, highest first.\n */\n return results.sort(function (a, b) {\n return b.score - a.score\n })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n var invertedIndex = Object.keys(this.invertedIndex)\n .sort()\n .map(function (term) {\n return [term, this.invertedIndex[term]]\n }, this)\n\n var fieldVectors = Object.keys(this.fieldVectors)\n .map(function (ref) {\n return [ref, this.fieldVectors[ref].toJSON()]\n }, this)\n\n return {\n version: lunr.version,\n fields: this.fields,\n fieldVectors: fieldVectors,\n invertedIndex: invertedIndex,\n pipeline: this.pipeline.toJSON()\n }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n var attrs = {},\n fieldVectors = {},\n serializedVectors = serializedIndex.fieldVectors,\n invertedIndex = Object.create(null),\n serializedInvertedIndex = serializedIndex.invertedIndex,\n tokenSetBuilder = new lunr.TokenSet.Builder,\n pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n if (serializedIndex.version != lunr.version) {\n lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n }\n\n for (var i = 0; i < serializedVectors.length; i++) {\n var tuple = serializedVectors[i],\n ref = tuple[0],\n elements = tuple[1]\n\n fieldVectors[ref] = new lunr.Vector(elements)\n }\n\n for (var i = 0; i < serializedInvertedIndex.length; i++) {\n var tuple = serializedInvertedIndex[i],\n term = tuple[0],\n posting = tuple[1]\n\n tokenSetBuilder.insert(term)\n invertedIndex[term] = posting\n }\n\n tokenSetBuilder.finish()\n\n attrs.fields = serializedIndex.fields\n\n attrs.fieldVectors = fieldVectors\n attrs.invertedIndex = invertedIndex\n attrs.tokenSet = tokenSetBuilder.root\n attrs.pipeline = pipeline\n\n return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n this._ref = \"id\"\n this._fields = Object.create(null)\n this._documents = Object.create(null)\n this.invertedIndex = Object.create(null)\n this.fieldTermFrequencies = {}\n this.fieldLengths = {}\n this.tokenizer = lunr.tokenizer\n this.pipeline = new lunr.Pipeline\n this.searchPipeline = new lunr.Pipeline\n this.documentCount = 0\n this._b = 0.75\n this._k1 = 1.2\n this.termIndex = 0\n this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example Extracting a nested field \n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n if (/\\//.test(fieldName)) {\n throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n }\n\n this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n if (number < 0) {\n this._b = 0\n } else if (number > 1) {\n this._b = 1\n } else {\n this._b = number\n }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n var docRef = doc[this._ref],\n fields = Object.keys(this._fields)\n\n this._documents[docRef] = attributes || {}\n this.documentCount += 1\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i],\n extractor = this._fields[fieldName].extractor,\n field = extractor ? extractor(doc) : doc[fieldName],\n tokens = this.tokenizer(field, {\n fields: [fieldName]\n }),\n terms = this.pipeline.run(tokens),\n fieldRef = new lunr.FieldRef (docRef, fieldName),\n fieldTerms = Object.create(null)\n\n this.fieldTermFrequencies[fieldRef] = fieldTerms\n this.fieldLengths[fieldRef] = 0\n\n // store the length of this field for this document\n this.fieldLengths[fieldRef] += terms.length\n\n // calculate term frequencies for this field\n for (var j = 0; j < terms.length; j++) {\n var term = terms[j]\n\n if (fieldTerms[term] == undefined) {\n fieldTerms[term] = 0\n }\n\n fieldTerms[term] += 1\n\n // add to inverted index\n // create an initial posting if one doesn't exist\n if (this.invertedIndex[term] == undefined) {\n var posting = Object.create(null)\n posting[\"_index\"] = this.termIndex\n this.termIndex += 1\n\n for (var k = 0; k < fields.length; k++) {\n posting[fields[k]] = Object.create(null)\n }\n\n this.invertedIndex[term] = posting\n }\n\n // add an entry for this term/fieldName/docRef to the invertedIndex\n if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n }\n\n // store all whitelisted metadata about this token in the\n // inverted index\n for (var l = 0; l < this.metadataWhitelist.length; l++) {\n var metadataKey = this.metadataWhitelist[l],\n metadata = term.metadata[metadataKey]\n\n if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n }\n\n this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n }\n }\n\n }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n var fieldRefs = Object.keys(this.fieldLengths),\n numberOfFields = fieldRefs.length,\n accumulator = {},\n documentsWithField = {}\n\n for (var i = 0; i < numberOfFields; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n field = fieldRef.fieldName\n\n documentsWithField[field] || (documentsWithField[field] = 0)\n documentsWithField[field] += 1\n\n accumulator[field] || (accumulator[field] = 0)\n accumulator[field] += this.fieldLengths[fieldRef]\n }\n\n var fields = Object.keys(this._fields)\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i]\n accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n }\n\n this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n var fieldVectors = {},\n fieldRefs = Object.keys(this.fieldTermFrequencies),\n fieldRefsLength = fieldRefs.length,\n termIdfCache = Object.create(null)\n\n for (var i = 0; i < fieldRefsLength; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n fieldName = fieldRef.fieldName,\n fieldLength = this.fieldLengths[fieldRef],\n fieldVector = new lunr.Vector,\n termFrequencies = this.fieldTermFrequencies[fieldRef],\n terms = Object.keys(termFrequencies),\n termsLength = terms.length\n\n\n var fieldBoost = this._fields[fieldName].boost || 1,\n docBoost = this._documents[fieldRef.docRef].boost || 1\n\n for (var j = 0; j < termsLength; j++) {\n var term = terms[j],\n tf = termFrequencies[term],\n termIndex = this.invertedIndex[term]._index,\n idf, score, scoreWithPrecision\n\n if (termIdfCache[term] === undefined) {\n idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n termIdfCache[term] = idf\n } else {\n idf = termIdfCache[term]\n }\n\n score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n score *= fieldBoost\n score *= docBoost\n scoreWithPrecision = Math.round(score * 1000) / 1000\n // Converts 1.23456789 to 1.234.\n // Reducing the precision so that the vectors take up less\n // space when serialised. Doing it now so that they behave\n // the same before and after serialisation. Also, this is\n // the fastest approach to reducing a number's precision in\n // JavaScript.\n\n fieldVector.insert(termIndex, scoreWithPrecision)\n }\n\n fieldVectors[fieldRef] = fieldVector\n }\n\n this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n this.tokenSet = lunr.TokenSet.fromArray(\n Object.keys(this.invertedIndex).sort()\n )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n this.calculateAverageFieldLengths()\n this.createFieldVectors()\n this.createTokenSet()\n\n return new lunr.Index({\n invertedIndex: this.invertedIndex,\n fieldVectors: this.fieldVectors,\n tokenSet: this.tokenSet,\n fields: Object.keys(this._fields),\n pipeline: this.searchPipeline\n })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n var args = Array.prototype.slice.call(arguments, 1)\n args.unshift(this)\n fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n var clonedMetadata = Object.create(null),\n metadataKeys = Object.keys(metadata || {})\n\n // Cloning the metadata to prevent the original\n // being mutated during match data combination.\n // Metadata is kept in an array within the inverted\n // index so cloning the data can be done with\n // Array#slice\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n clonedMetadata[key] = metadata[key].slice()\n }\n\n this.metadata = Object.create(null)\n\n if (term !== undefined) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = clonedMetadata\n }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n var terms = Object.keys(otherMatchData.metadata)\n\n for (var i = 0; i < terms.length; i++) {\n var term = terms[i],\n fields = Object.keys(otherMatchData.metadata[term])\n\n if (this.metadata[term] == undefined) {\n this.metadata[term] = Object.create(null)\n }\n\n for (var j = 0; j < fields.length; j++) {\n var field = fields[j],\n keys = Object.keys(otherMatchData.metadata[term][field])\n\n if (this.metadata[term][field] == undefined) {\n this.metadata[term][field] = Object.create(null)\n }\n\n for (var k = 0; k < keys.length; k++) {\n var key = keys[k]\n\n if (this.metadata[term][field][key] == undefined) {\n this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n } else {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n }\n\n }\n }\n }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n if (!(term in this.metadata)) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = metadata\n return\n }\n\n if (!(field in this.metadata[term])) {\n this.metadata[term][field] = metadata\n return\n }\n\n var metadataKeys = Object.keys(metadata)\n\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n\n if (key in this.metadata[term][field]) {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n } else {\n this.metadata[term][field][key] = metadata[key]\n }\n }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n this.clauses = []\n this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with trailing wildcard \n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example query term with leading and trailing wildcard \n * query.term('foo', {\n * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with required presence \n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n /**\n * Term's presence in a document is optional, this is the default value.\n */\n OPTIONAL: 1,\n\n /**\n * Term's presence in a document is required, documents that do not contain\n * this term will not be returned.\n */\n REQUIRED: 2,\n\n /**\n * Term's presence in a document is prohibited, documents that do contain\n * this term will not be returned.\n */\n PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n if (!('fields' in clause)) {\n clause.fields = this.allFields\n }\n\n if (!('boost' in clause)) {\n clause.boost = 1\n }\n\n if (!('usePipeline' in clause)) {\n clause.usePipeline = true\n }\n\n if (!('wildcard' in clause)) {\n clause.wildcard = lunr.Query.wildcard.NONE\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n clause.term = \"*\" + clause.term\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n clause.term = \"\" + clause.term + \"*\"\n }\n\n if (!('presence' in clause)) {\n clause.presence = lunr.Query.presence.OPTIONAL\n }\n\n this.clauses.push(clause)\n\n return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n for (var i = 0; i < this.clauses.length; i++) {\n if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example adding a single term to a query \n * query.term(\"foo\")\n * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard \n * query.term(\"foo\", {\n * fields: [\"title\"],\n * boost: 10,\n * wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example using lunr.tokenizer to convert a string to tokens before using them as terms \n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n if (Array.isArray(term)) {\n term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n return this\n }\n\n var clause = options || {}\n clause.term = term.toString()\n\n this.clause(clause)\n\n return this\n}\nlunr.QueryParseError = function (message, start, end) {\n this.name = \"QueryParseError\"\n this.message = message\n this.start = start\n this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n this.lexemes = []\n this.str = str\n this.length = str.length\n this.pos = 0\n this.start = 0\n this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n var state = lunr.QueryLexer.lexText\n\n while (state) {\n state = state(this)\n }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n var subSlices = [],\n sliceStart = this.start,\n sliceEnd = this.pos\n\n for (var i = 0; i < this.escapeCharPositions.length; i++) {\n sliceEnd = this.escapeCharPositions[i]\n subSlices.push(this.str.slice(sliceStart, sliceEnd))\n sliceStart = sliceEnd + 1\n }\n\n subSlices.push(this.str.slice(sliceStart, this.pos))\n this.escapeCharPositions.length = 0\n\n return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n this.lexemes.push({\n type: type,\n str: this.sliceString(),\n start: this.start,\n end: this.pos\n })\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n this.escapeCharPositions.push(this.pos - 1)\n this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n if (this.pos >= this.length) {\n return lunr.QueryLexer.EOS\n }\n\n var char = this.str.charAt(this.pos)\n this.pos += 1\n return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n if (this.start == this.pos) {\n this.pos += 1\n }\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n var char, charCode\n\n do {\n char = this.next()\n charCode = char.charCodeAt(0)\n } while (charCode > 47 && charCode < 58)\n\n if (char != lunr.QueryLexer.EOS) {\n this.backup()\n }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.FIELD)\n lexer.ignore()\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n if (lexer.width() > 1) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.TERM)\n }\n\n lexer.ignore()\n\n if (lexer.more()) {\n return lunr.QueryLexer.lexText\n }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.BOOST)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n while (true) {\n var char = lexer.next()\n\n if (char == lunr.QueryLexer.EOS) {\n return lunr.QueryLexer.lexEOS\n }\n\n // Escape character is '\\'\n if (char.charCodeAt(0) == 92) {\n lexer.escapeCharacter()\n continue\n }\n\n if (char == \":\") {\n return lunr.QueryLexer.lexField\n }\n\n if (char == \"~\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexEditDistance\n }\n\n if (char == \"^\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexBoost\n }\n\n // \"+\" indicates term presence is required\n // checking for length to ensure that only\n // leading \"+\" are considered\n if (char == \"+\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n // \"-\" indicates term presence is prohibited\n // checking for length to ensure that only\n // leading \"-\" are considered\n if (char == \"-\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n if (char.match(lunr.QueryLexer.termSeparator)) {\n return lunr.QueryLexer.lexTerm\n }\n }\n}\n\nlunr.QueryParser = function (str, query) {\n this.lexer = new lunr.QueryLexer (str)\n this.query = query\n this.currentClause = {}\n this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n this.lexer.run()\n this.lexemes = this.lexer.lexemes\n\n var state = lunr.QueryParser.parseClause\n\n while (state) {\n state = state(this)\n }\n\n return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n var lexeme = this.peekLexeme()\n this.lexemeIdx += 1\n return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n var completedClause = this.currentClause\n this.query.clause(completedClause)\n this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n var lexeme = parser.peekLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.type) {\n case lunr.QueryLexer.PRESENCE:\n return lunr.QueryParser.parsePresence\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n if (lexeme.str.length >= 1) {\n errorMessage += \" with value '\" + lexeme.str + \"'\"\n }\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.str) {\n case \"-\":\n parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n break\n case \"+\":\n parser.currentClause.presence = lunr.Query.presence.REQUIRED\n break\n default:\n var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term or field, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.fields = [lexeme.str]\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n parser.currentClause.term = lexeme.str.toLowerCase()\n\n if (lexeme.str.indexOf(\"*\") != -1) {\n parser.currentClause.usePipeline = false\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var editDistance = parseInt(lexeme.str, 10)\n\n if (isNaN(editDistance)) {\n var errorMessage = \"edit distance must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.editDistance = editDistance\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var boost = parseInt(lexeme.str, 10)\n\n if (isNaN(boost)) {\n var errorMessage = \"boost must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.boost = boost\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\n /**\n * export the module via AMD, CommonJS or as a browser global\n * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n */\n ;(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define(factory)\n } else if (typeof exports === 'object') {\n /**\n * Node. Does not work with strict CommonJS, but\n * only CommonJS-like enviroments that support module.exports,\n * like Node.\n */\n module.exports = factory()\n } else {\n // Browser globals (root is window)\n root.lunr = factory()\n }\n }(this, function () {\n /**\n * Just return a value to define the module export.\n * This example returns an object, but the module\n * can return a function as the exported value.\n */\n return lunr\n }))\n})();\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport { getElement } from \"~/browser/element/_\"\nimport \"~/polyfills\"\n\nimport { Search } from \"../../_\"\nimport { SearchConfig } from \"../../config\"\nimport {\n SearchMessage,\n SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for `iframe-worker` shim\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous shim implementation. The cool\n * thing is that awaiting a non-Promise will convert it into a Promise, so\n * extending the type definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n *\n * @param urls - Scripts to load\n *\n * @returns Promise resolving with no result\n */\ndeclare global {\n function importScripts(...urls: string[]): Promise | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages which are defined as part of the search configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n config: SearchConfig\n): Promise {\n let base = \"../lunr\"\n\n /* Detect `iframe-worker` and fix base URL */\n if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n const worker = getElement(\"script[src]\")\n const [path] = worker.src.split(\"/worker\")\n\n /* Prefix base with path */\n base = base.replace(\"..\", path)\n }\n\n /* Add scripts for languages */\n const scripts = []\n for (const lang of config.lang) {\n switch (lang) {\n\n /* Add segmenter for Japanese */\n case \"ja\":\n scripts.push(`${base}/tinyseg.js`)\n break\n\n /* Add segmenter for Hindi and Thai */\n case \"hi\":\n case \"th\":\n scripts.push(`${base}/wordcut.js`)\n break\n }\n\n /* Add language support */\n if (lang !== \"en\")\n scripts.push(`${base}/min/lunr.${lang}.min.js`)\n }\n\n /* Add multi-language support */\n if (config.lang.length > 1)\n scripts.push(`${base}/min/lunr.multi.min.js`)\n\n /* Load scripts synchronously */\n if (scripts.length)\n await importScripts(\n `${base}/min/lunr.stemmer.support.min.js`,\n ...scripts\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n message: SearchMessage\n): Promise {\n switch (message.type) {\n\n /* Search setup message */\n case SearchMessageType.SETUP:\n await setupSearchLanguages(message.data.config)\n index = new Search(message.data)\n return {\n type: SearchMessageType.READY\n }\n\n /* Search query message */\n case SearchMessageType.QUERY:\n const query = message.data\n try {\n return {\n type: SearchMessageType.RESULT,\n data: index.search(query)\n }\n\n /* Return empty result in case of error */\n } catch (err) {\n console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n console.warn(err)\n return {\n type: SearchMessageType.RESULT,\n data: { items: [] }\n }\n }\n\n /* All other messages */\n default:\n throw new TypeError(\"Invalid message type\")\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* Expose Lunr.js in global scope, or stemmers won't work */\nself.lunr = lunr\n\n/* Monkey-patch Lunr.js to mitigate https://t.ly/68TLq */\nlunr.utils.warn = console.warn\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements(\n selector: string, node?: ParentNode\n): T[]\n\nexport function getElements(\n selector: string, node: ParentNode = document\n): T[] {\n return Array.from(node.querySelectorAll(selector))\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * Note that this function assumes that the element is present. If unsure if an\n * element is existent, use the `getOptionalElement` function instead.\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement(\n selector: string, node?: ParentNode\n): T\n\nexport function getElement(\n selector: string, node: ParentNode = document\n): T {\n const el = getOptionalElement(selector, node)\n if (typeof el === \"undefined\")\n throw new ReferenceError(\n `Missing element: expected \"${selector}\" to be present`\n )\n\n /* Return element */\n return el\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an optional element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getOptionalElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T] | undefined\n\nexport function getOptionalElement(\n selector: string, node?: ParentNode\n): T | undefined\n\nexport function getOptionalElement(\n selector: string, node: ParentNode = document\n): T | undefined {\n return node.querySelector(selector) || undefined\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n return (\n document.activeElement?.shadowRoot?.activeElement as HTMLElement ??\n document.activeElement as HTMLElement ??\n undefined\n )\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Polyfills\n * ------------------------------------------------------------------------- */\n\n/* Polyfill `Object.entries` */\nif (!Object.entries)\n Object.entries = function (obj: object) {\n const data: [string, string][] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push([key, obj[key]])\n\n /* Return entries */\n return data\n }\n\n/* Polyfill `Object.values` */\nif (!Object.values)\n Object.values = function (obj: object) {\n const data: string[] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push(obj[key])\n\n /* Return values */\n return data\n }\n\n/* ------------------------------------------------------------------------- */\n\n/* Polyfills for `Element` */\nif (typeof Element !== \"undefined\") {\n\n /* Polyfill `Element.scrollTo` */\n if (!Element.prototype.scrollTo)\n Element.prototype.scrollTo = function (\n x?: ScrollToOptions | number, y?: number\n ): void {\n if (typeof x === \"object\") {\n this.scrollLeft = x.left!\n this.scrollTop = x.top!\n } else {\n this.scrollLeft = x!\n this.scrollTop = y!\n }\n }\n\n /* Polyfill `Element.replaceWith` */\n if (!Element.prototype.replaceWith)\n Element.prototype.replaceWith = function (\n ...nodes: Array\n ): void {\n const parent = this.parentNode\n if (parent) {\n if (nodes.length === 0)\n parent.removeChild(this)\n\n /* Replace children and create text nodes */\n for (let i = nodes.length - 1; i >= 0; i--) {\n let node = nodes[i]\n if (typeof node === \"string\")\n node = document.createTextNode(node)\n else if (node.parentNode)\n node.parentNode.removeChild(node)\n\n /* Replace child or insert before previous sibling */\n if (!i)\n parent.replaceChild(node, this)\n else\n parent.insertBefore(this.previousSibling!, node)\n }\n }\n }\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search configuration\n */\nexport interface SearchConfig {\n lang: string[] /* Search languages */\n separator: string /* Search separator */\n pipeline: SearchPipelineFn[] /* Search pipeline */\n}\n\n/**\n * Search document\n */\nexport interface SearchDocument {\n location: string /* Document location */\n title: string /* Document title */\n text: string /* Document text */\n tags?: string[] /* Document tags */\n boost?: number /* Document boost */\n parent?: SearchDocument /* Document parent */\n}\n\n/**\n * Search options\n */\nexport interface SearchOptions {\n suggest: boolean /* Search suggestions */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport interface SearchIndex {\n config: SearchConfig /* Search configuration */\n docs: SearchDocument[] /* Search documents */\n options: SearchOptions /* Search options */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search pipeline function\n */\ntype SearchPipelineFn =\n | \"trimmer\" /* Trimmer */\n | \"stopWordFilter\" /* Stop word filter */\n | \"stemmer\" /* Stemmer */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document map\n *\n * This function creates a mapping of URLs (including anchors) to the actual\n * articles and sections. It relies on the invariant that the search index is\n * ordered with the main article appearing before all sections with anchors.\n * If this is not the case, the logic music be changed.\n *\n * @param docs - Search documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchDocument[]\n): Map {\n const map = new Map()\n for (const doc of docs) {\n const [path] = doc.location.split(\"#\")\n\n /* Add document article */\n const article = map.get(path)\n if (typeof article === \"undefined\") {\n map.set(path, doc)\n\n /* Add document section */\n } else {\n map.set(doc.location, doc)\n doc.parent = article\n }\n }\n\n /* Return search document map */\n return map\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Visitor function\n *\n * @param start - Start offset\n * @param end - End offset\n */\ntype VisitorFn = (\n start: number, end: number\n) => void\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Split a string using the given separator\n *\n * @param input - Input value\n * @param separator - Separator\n * @param fn - Visitor function\n */\nexport function split(\n input: string, separator: RegExp, fn: VisitorFn\n): void {\n separator = new RegExp(separator, \"g\")\n\n /* Split string using separator */\n let match: RegExpExecArray | null\n let index = 0\n do {\n match = separator.exec(input)\n\n /* Emit non-empty range */\n const until = match?.index ?? input.length\n if (index < until)\n fn(index, until)\n\n /* Update last index */\n if (match) {\n const [term] = match\n index = match.index + term.length\n\n /* Support zero-length lookaheads */\n if (term.length === 0)\n separator.lastIndex = match.index + 1\n }\n } while (match)\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Extraction type\n *\n * This type defines the possible values that are encoded into the first two\n * bits of a section that is part of the blocks of a tokenization table. There\n * are three types of interest: HTML opening and closing tags, as well as the\n * actual text content we need to extract for indexing.\n */\nexport const enum Extract {\n TAG_OPEN = 0, /* HTML opening tag */\n TEXT = 1, /* Text content */\n TAG_CLOSE = 2 /* HTML closing tag */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Visitor function\n *\n * @param block - Block index\n * @param type - Extraction type\n * @param start - Start offset\n * @param end - End offset\n */\ntype VisitorFn = (\n block: number, type: Extract, start: number, end: number\n) => void\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Split a string into markup and text sections\n *\n * This function scans a string and divides it up into sections of markup and\n * text. For each section, it invokes the given visitor function with the block\n * index, extraction type, as well as start and end offsets. Using a visitor\n * function (= streaming data) is ideal for minimizing pressure on the GC.\n *\n * @param input - Input value\n * @param fn - Visitor function\n */\nexport function extract(\n input: string, fn: VisitorFn\n): void {\n\n let block = 0 /* Current block */\n let start = 0 /* Current start offset */\n let end = 0 /* Current end offset */\n\n /* Split string into sections */\n for (let stack = 0; end < input.length; end++) {\n\n /* Opening tag after non-empty section */\n if (input.charAt(end) === \"<\" && end > start) {\n fn(block, Extract.TEXT, start, start = end)\n\n /* Closing tag */\n } else if (input.charAt(end) === \">\") {\n if (input.charAt(start + 1) === \"/\") {\n if (--stack === 0)\n fn(block++, Extract.TAG_CLOSE, start, end + 1)\n\n /* Tag is not self-closing */\n } else if (input.charAt(end - 1) !== \"/\") {\n if (stack++ === 0)\n fn(block, Extract.TAG_OPEN, start, end + 1)\n }\n\n /* New section */\n start = end + 1\n }\n }\n\n /* Add trailing section */\n if (end > start)\n fn(block, Extract.TEXT, start, end)\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Position table\n */\nexport type PositionTable = number[][]\n\n/**\n * Position\n */\nexport type Position = number\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Highlight all occurrences in a string\n *\n * This function receives a field's value (e.g. like `title` or `text`), it's\n * position table that was generated during indexing, and the positions found\n * when executing the query. It then highlights all occurrences, and returns\n * their concatenation. In case of multiple blocks, two are returned.\n *\n * @param input - Input value\n * @param table - Table for indexing\n * @param positions - Occurrences\n * @param full - Full results\n *\n * @returns Highlighted string value\n */\nexport function highlight(\n input: string, table: PositionTable, positions: Position[], full = false\n): string {\n return highlightAll([input], table, positions, full).pop()!\n}\n\n/**\n * Highlight all occurrences in a set of strings\n *\n * @param inputs - Input values\n * @param table - Table for indexing\n * @param positions - Occurrences\n * @param full - Full results\n *\n * @returns Highlighted string values\n */\nexport function highlightAll(\n inputs: string[], table: PositionTable, positions: Position[], full = false\n): string[] {\n\n /* Map blocks to input values */\n const mapping = [0]\n for (let t = 1; t < table.length; t++) {\n const prev = table[t - 1]\n const next = table[t]\n\n /* Check if table points to new block */\n const p = prev[prev.length - 1] >>> 2 & 0x3FF\n const q = next[0] >>> 12\n\n /* Add block to mapping */\n mapping.push(+(p > q) + mapping[mapping.length - 1])\n }\n\n /* Highlight strings one after another */\n return inputs.map((input, i) => {\n let cursor = 0\n\n /* Map occurrences to blocks */\n const blocks = new Map()\n for (const p of positions.sort((a, b) => a - b)) {\n const index = p & 0xFFFFF\n const block = p >>> 20\n if (mapping[block] !== i)\n continue\n\n /* Ensure presence of block group */\n let group = blocks.get(block)\n if (typeof group === \"undefined\")\n blocks.set(block, group = [])\n\n /* Add index to group */\n group.push(index)\n }\n\n /* Just return string, if no occurrences */\n if (blocks.size === 0)\n return input\n\n /* Compute slices */\n const slices: string[] = []\n for (const [block, indexes] of blocks) {\n const t = table[block]\n\n /* Extract positions and length */\n const start = t[0] >>> 12\n const end = t[t.length - 1] >>> 12\n const length = t[t.length - 1] >>> 2 & 0x3FF\n\n /* Add prefix, if full results are desired */\n if (full && start > cursor)\n slices.push(input.slice(cursor, start))\n\n /* Extract and highlight slice */\n let slice = input.slice(start, end + length)\n for (const j of indexes.sort((a, b) => b - a)) {\n\n /* Retrieve offset and length of match */\n const p = (t[j] >>> 12) - start\n const q = (t[j] >>> 2 & 0x3FF) + p\n\n /* Wrap occurrence */\n slice = [\n slice.slice(0, p),\n \"\",\n slice.slice(p, q),\n \"\",\n slice.slice(q)\n ].join(\"\")\n }\n\n /* Update cursor */\n cursor = end + length\n\n /* Append slice and abort if we have two */\n if (slices.push(slice) === 2)\n break\n }\n\n /* Add suffix, if full results are desired */\n if (full && cursor < input.length)\n slices.push(input.slice(cursor))\n\n /* Return highlighted slices */\n return slices.join(\"\")\n })\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { split } from \"../_\"\nimport {\n Extract,\n extract\n} from \"../extract\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Split a string or set of strings into tokens\n *\n * This tokenizer supersedes the default tokenizer that is provided by Lunr.js,\n * as it is aware of HTML tags and allows for multi-character splitting.\n *\n * It takes the given inputs, splits each of them into markup and text sections,\n * tokenizes and segments (if necessary) each of them, and then indexes them in\n * a table by using a compact bit representation. Bitwise techniques are used\n * to write and read from the table during indexing and querying.\n *\n * @see https://bit.ly/3W3Xw4J - Search: better, faster, smaller\n *\n * @param input - Input value(s)\n *\n * @returns Tokens\n */\nexport function tokenize(\n input?: string | string[]\n): lunr.Token[] {\n const tokens: lunr.Token[] = []\n if (typeof input === \"undefined\")\n return tokens\n\n /* Tokenize strings one after another */\n const inputs = Array.isArray(input) ? input : [input]\n for (let i = 0; i < inputs.length; i++) {\n const table = lunr.tokenizer.table\n const total = table.length\n\n /* Split string into sections and tokenize content blocks */\n extract(inputs[i], (block, type, start, end) => {\n table[block += total] ||= []\n switch (type) {\n\n /* Handle markup */\n case Extract.TAG_OPEN:\n case Extract.TAG_CLOSE:\n table[block].push(\n start << 12 |\n end - start << 2 |\n type\n )\n break\n\n /* Handle text content */\n case Extract.TEXT:\n const section = inputs[i].slice(start, end)\n split(section, lunr.tokenizer.separator, (index, until) => {\n\n /**\n * Apply segmenter after tokenization. Note that the segmenter will\n * also split words at word boundaries, which is not what we want,\n * so we need to check if we can somehow mitigate this behavior.\n */\n if (typeof lunr.segmenter !== \"undefined\") {\n const subsection = section.slice(index, until)\n if (/^[MHIK]$/.test(lunr.segmenter.ctype_(subsection))) {\n const segments = lunr.segmenter.segment(subsection)\n for (let s = 0, l = 0; s < segments.length; s++) {\n\n /* Add block to section */\n table[block] ||= []\n table[block].push(\n start + index + l << 12 |\n segments[s].length << 2 |\n type\n )\n\n /* Add token with position */\n tokens.push(new lunr.Token(\n segments[s].toLowerCase(), {\n position: block << 20 | table[block].length - 1\n }\n ))\n\n /* Keep track of length */\n l += segments[s].length\n }\n return\n }\n }\n\n /* Add block to section */\n table[block].push(\n start + index << 12 |\n until - index << 2 |\n type\n )\n\n /* Add token with position */\n tokens.push(new lunr.Token(\n section.slice(index, until).toLowerCase(), {\n position: block << 20 | table[block].length - 1\n }\n ))\n })\n }\n })\n }\n\n /* Return tokens */\n return tokens\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Visitor function\n *\n * @param value - String value\n *\n * @returns String term(s)\n */\ntype VisitorFn = (\n value: string\n) => string | string[]\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Trim excess whitespace from left and right.\n *\n * 2. Search for parts in quotation marks and prepend a `+` modifier to denote\n * that the resulting document must contain all parts, converting the query\n * to an `AND` query (as opposed to the default `OR` behavior). While users\n * may expect parts enclosed in quotation marks to map to span queries, i.e.\n * for which order is important, Lunr.js doesn't support them, so the best\n * we can do is to convert the parts to an `AND` query.\n *\n * 3. Replace control characters which are not located at the beginning of the\n * query or preceded by white space, or are not followed by a non-whitespace\n * character or are at the end of the query string. Furthermore, filter\n * unmatched quotation marks.\n *\n * 4. Split the query string at whitespace, then pass each part to the visitor\n * function for tokenization, and append a wildcard to every resulting term\n * that is not explicitly marked with a `+`, `-`, `~` or `^` modifier, since\n * it ensures consistent and stable ranking when multiple terms are entered.\n * Also, if a fuzzy or boost modifier are given, but no numeric value has\n * been entered, default to 1 to not induce a query error.\n *\n * @param query - Query value\n * @param fn - Visitor function\n *\n * @returns Transformed query value\n */\nexport function transform(\n query: string, fn: VisitorFn = term => term\n): string {\n return query\n\n /* => 1 */\n .trim()\n\n /* => 2 */\n .split(/\"([^\"]+)\"/g)\n .map((parts, index) => index & 1\n ? parts.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n : parts\n )\n .join(\"\")\n\n /* => 3 */\n .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\")\n\n /* => 4 */\n .split(/\\s+/g)\n .reduce((prev, term) => {\n const next = fn(term)\n return [...prev, ...Array.isArray(next) ? next : [next]]\n }, [] as string[])\n .map(term => /([~^]$)/.test(term) ? `${term}1` : term)\n .map(term => /(^[+-]|[~^]\\d+$)/.test(term) ? term : `${term}*`)\n .join(\" \")\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { split } from \"../../internal\"\nimport { transform } from \"../transform\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n presence: lunr.Query.presence /* Clause presence */\n term: string /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Transform search query\n *\n * This function lexes the given search query and applies the transformation\n * function to each term, preserving markup like `+` and `-` modifiers.\n *\n * @param query - Search query\n *\n * @returns Search query\n */\nexport function transformSearchQuery(\n query: string\n): string {\n\n /* Split query terms with tokenizer */\n return transform(query, part => {\n const terms: string[] = []\n\n /* Initialize lexer and analyze part */\n const lexer = new lunr.QueryLexer(part)\n lexer.run()\n\n /* Extract and tokenize term from lexeme */\n for (const { type, str: term, start, end } of lexer.lexemes)\n switch (type) {\n\n /* Hack: remove colon - see https://bit.ly/3wD3T3I */\n case \"FIELD\":\n if (![\"title\", \"text\", \"tags\"].includes(term))\n part = [\n part.slice(0, end),\n \" \",\n part.slice(end + 1)\n ].join(\"\")\n break\n\n /* Tokenize term */\n case \"TERM\":\n split(term, lunr.tokenizer.separator, (...range) => {\n terms.push([\n part.slice(0, start),\n term.slice(...range),\n part.slice(end)\n ].join(\"\"))\n })\n }\n\n /* Return terms */\n return terms\n })\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * Lunr.js itself has a bug where it doesn't detect or remove wildcards for\n * query clauses, so we must do this here.\n *\n * @see https://bit.ly/3DpTGtz - GitHub issue\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n value: string\n): SearchQueryClause[] {\n const query = new lunr.Query([\"title\", \"text\", \"tags\"])\n const parser = new lunr.QueryParser(value, query)\n\n /* Parse Search query */\n parser.parse()\n for (const clause of query.clauses) {\n clause.usePipeline = true\n\n /* Handle leading wildcard */\n if (clause.term.startsWith(\"*\")) {\n clause.wildcard = lunr.Query.wildcard.LEADING\n clause.term = clause.term.slice(1)\n }\n\n /* Handle trailing wildcard */\n if (clause.term.endsWith(\"*\")) {\n clause.wildcard = lunr.Query.wildcard.TRAILING\n clause.term = clause.term.slice(0, -1)\n }\n }\n\n /* Return query clauses */\n return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n const clauses = new Set(query)\n\n /* Match query clauses against terms */\n const result: SearchQueryTerms = {}\n for (let t = 0; t < terms.length; t++)\n for (const clause of clauses)\n if (terms[t].startsWith(clause.term)) {\n result[clause.term] = true\n clauses.delete(clause)\n }\n\n /* Annotate unmatched non-stopword query clauses */\n for (const clause of clauses)\n if (lunr.stopWordFilter?.(clause.term))\n result[clause.term] = false\n\n /* Return query terms */\n return result\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Segment a search query using the inverted index\n *\n * This function implements a clever approach to text segmentation for Asian\n * languages, as it used the information already available in the search index.\n * The idea is to greedily segment the search query based on the tokens that are\n * already part of the index, as described in the linked issue.\n *\n * @see https://bit.ly/3lwjrk7 - GitHub issue\n *\n * @param query - Query value\n * @param index - Inverted index\n *\n * @returns Segmented query value\n */\nexport function segment(\n query: string, index: object\n): Iterable {\n const segments = new Set()\n\n /* Segment search query */\n const wordcuts = new Uint16Array(query.length)\n for (let i = 0; i < query.length; i++)\n for (let j = i + 1; j < query.length; j++) {\n const value = query.slice(i, j)\n if (value in index)\n wordcuts[i] = j - i\n }\n\n /* Compute longest matches with minimum overlap */\n const stack = [0]\n for (let s = stack.length; s > 0;) {\n const p = stack[--s]\n for (let q = 1; q < wordcuts[p]; q++)\n if (wordcuts[p + q] > wordcuts[p] - q) {\n segments.add(query.slice(p, p + q))\n stack[s++] = p + q\n }\n\n /* Continue at end of query string */\n const q = p + wordcuts[p]\n if (wordcuts[q] && q < query.length - 1)\n stack[s++] = q\n\n /* Add current segment */\n segments.add(query.slice(p, q))\n }\n\n // @todo fix this case in the code block above, this is a hotfix\n if (segments.has(\"\"))\n return new Set([query])\n\n /* Return segmented query value */\n return segments\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n SearchDocument,\n SearchIndex,\n SearchOptions,\n setupSearchDocumentMap\n} from \"../config\"\nimport {\n Position,\n PositionTable,\n highlight,\n highlightAll,\n tokenize\n} from \"../internal\"\nimport {\n SearchQueryTerms,\n getSearchQueryTerms,\n parseSearchQuery,\n segment,\n transformSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search item\n */\nexport interface SearchItem\n extends SearchDocument\n{\n score: number /* Score (relevance) */\n terms: SearchQueryTerms /* Search query terms */\n}\n\n/**\n * Search result\n */\nexport interface SearchResult {\n items: SearchItem[][] /* Search items */\n suggest?: string[] /* Search suggestions */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create field extractor factory\n *\n * @param table - Position table map\n *\n * @returns Extractor factory\n */\nfunction extractor(table: Map) {\n return (name: keyof SearchDocument) => {\n return (doc: SearchDocument) => {\n if (typeof doc[name] === \"undefined\")\n return undefined\n\n /* Compute identifier and initialize table */\n const id = [doc.location, name].join(\":\")\n table.set(id, lunr.tokenizer.table = [])\n\n /* Return field value */\n return doc[name]\n }\n }\n}\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n const [x, y] = [new Set(a), new Set(b)]\n return [\n ...new Set([...x].filter(value => !y.has(value)))\n ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n /**\n * Search document map\n */\n protected map: Map\n\n /**\n * Search options\n */\n protected options: SearchOptions\n\n /**\n * The underlying Lunr.js search index\n */\n protected index: lunr.Index\n\n /**\n * Internal position table map\n */\n protected table: Map\n\n /**\n * Create the search integration\n *\n * @param data - Search index\n */\n public constructor({ config, docs, options }: SearchIndex) {\n const field = extractor(this.table = new Map())\n\n /* Set up document map and options */\n this.map = setupSearchDocumentMap(docs)\n this.options = options\n\n /* Set up document index */\n this.index = lunr(function () {\n this.metadataWhitelist = [\"position\"]\n this.b(0)\n\n /* Set up (multi-)language support */\n if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n // @ts-expect-error - namespace indexing not supported\n this.use(lunr[config.lang[0]])\n } else if (config.lang.length > 1) {\n this.use(lunr.multiLanguage(...config.lang))\n }\n\n /* Set up custom tokenizer (must be after language setup) */\n this.tokenizer = tokenize as typeof lunr.tokenizer\n lunr.tokenizer.separator = new RegExp(config.separator)\n\n /* Set up custom segmenter, if loaded */\n lunr.segmenter = \"TinySegmenter\" in lunr\n ? new lunr.TinySegmenter()\n : undefined\n\n /* Compute functions to be removed from the pipeline */\n const fns = difference([\n \"trimmer\", \"stopWordFilter\", \"stemmer\"\n ], config.pipeline)\n\n /* Remove functions from the pipeline for registered languages */\n for (const lang of config.lang.map(language => (\n // @ts-expect-error - namespace indexing not supported\n language === \"en\" ? lunr : lunr[language]\n )))\n for (const fn of fns) {\n this.pipeline.remove(lang[fn])\n this.searchPipeline.remove(lang[fn])\n }\n\n /* Set up index reference */\n this.ref(\"location\")\n\n /* Set up index fields */\n this.field(\"title\", { boost: 1e3, extractor: field(\"title\") })\n this.field(\"text\", { boost: 1e0, extractor: field(\"text\") })\n this.field(\"tags\", { boost: 1e6, extractor: field(\"tags\") })\n\n /* Add documents to index */\n for (const doc of docs)\n this.add(doc, { boost: doc.boost })\n })\n }\n\n /**\n * Search for matching documents\n *\n * @param query - Search query\n *\n * @returns Search result\n */\n public search(query: string): SearchResult {\n\n // Experimental Chinese segmentation\n query = query.replace(/\\p{sc=Han}+/gu, value => {\n return [...segment(value, this.index.invertedIndex)]\n .join(\"* \")\n })\n\n // @todo: move segmenter (above) into transformSearchQuery\n query = transformSearchQuery(query)\n if (!query)\n return { items: [] }\n\n /* Parse query to extract clauses for analysis */\n const clauses = parseSearchQuery(query)\n .filter(clause => (\n clause.presence !== lunr.Query.presence.PROHIBITED\n ))\n\n /* Perform search and post-process results */\n const groups = this.index.search(query)\n\n /* Apply post-query boosts based on title and search query terms */\n .reduce((item, { ref, score, matchData }) => {\n let doc = this.map.get(ref)\n if (typeof doc !== \"undefined\") {\n\n /* Shallow copy document */\n doc = { ...doc }\n if (doc.tags)\n doc.tags = [...doc.tags]\n\n /* Compute and analyze search query terms */\n const terms = getSearchQueryTerms(\n clauses,\n Object.keys(matchData.metadata)\n )\n\n /* Highlight matches in fields */\n for (const field of this.index.fields) {\n if (typeof doc[field] === \"undefined\")\n continue\n\n /* Collect positions from matches */\n const positions: Position[] = []\n for (const match of Object.values(matchData.metadata))\n if (typeof match[field] !== \"undefined\")\n positions.push(...match[field].position)\n\n /* Skip highlighting, if no positions were collected */\n if (!positions.length)\n continue\n\n /* Load table and determine highlighting method */\n const table = this.table.get([doc.location, field].join(\":\"))!\n const fn = Array.isArray(doc[field])\n ? highlightAll\n : highlight\n\n // @ts-expect-error - stop moaning, TypeScript!\n doc[field] = fn(doc[field], table, positions, field !== \"text\")\n }\n\n /* Highlight title and text and apply post-query boosts */\n const boost = +!doc.parent +\n Object.values(terms)\n .filter(t => t).length /\n Object.keys(terms).length\n\n /* Append item */\n item.push({\n ...doc,\n score: score * (1 + boost ** 2),\n terms\n })\n }\n return item\n }, [])\n\n /* Sort search results again after applying boosts */\n .sort((a, b) => b.score - a.score)\n\n /* Group search results by article */\n .reduce((items, result) => {\n const doc = this.map.get(result.location)\n if (typeof doc !== \"undefined\") {\n const ref = doc.parent\n ? doc.parent.location\n : doc.location\n items.set(ref, [...items.get(ref) || [], result])\n }\n return items\n }, new Map())\n\n /* Ensure that every item set has an article */\n for (const [ref, items] of groups)\n if (!items.find(item => item.location === ref)) {\n const doc = this.map.get(ref)!\n items.push({ ...doc, score: 0, terms: {} })\n }\n\n /* Generate search suggestions, if desired */\n let suggest: string[] | undefined\n if (this.options.suggest) {\n const titles = this.index.query(builder => {\n for (const clause of clauses)\n builder.term(clause.term, {\n fields: [\"title\"],\n presence: lunr.Query.presence.REQUIRED,\n wildcard: lunr.Query.wildcard.TRAILING\n })\n })\n\n /* Retrieve suggestions for best match */\n suggest = titles.length\n ? Object.keys(titles[0].matchData.metadata)\n : []\n }\n\n /* Return search result */\n return {\n items: [...groups.values()],\n ...typeof suggest !== \"undefined\" && { suggest }\n }\n }\n}\n"],
+ "mappings": "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",
"names": ["require_lunr", "__commonJSMin", "exports", "module", "lunr", "config", "builder", "global", "message", "obj", "clone", "keys", "key", "val", "docRef", "fieldName", "stringValue", "s", "n", "fieldRef", "elements", "i", "other", "object", "a", "b", "intersection", "element", "posting", "documentCount", "documentsWithTerm", "x", "str", "metadata", "fn", "t", "len", "tokens", "sliceEnd", "sliceStart", "char", "sliceLength", "tokenMetadata", "label", "isRegistered", "serialised", "pipeline", "fnName", "fns", "existingFn", "newFn", "pos", "stackLength", "memo", "j", "result", "k", "token", "index", "start", "end", "pivotPoint", "pivotIndex", "insertIdx", "position", "sumOfSquares", "elementsLength", "otherVector", "dotProduct", "aLen", "bLen", "aVal", "bVal", "output", "step2list", "step3list", "c", "v", "C", "V", "mgr0", "meq1", "mgr1", "s_v", "re_mgr0", "re_mgr1", "re_meq1", "re_s_v", "re_1a", "re2_1a", "re_1b", "re2_1b", "re_1b_2", "re2_1b_2", "re3_1b_2", "re4_1b_2", "re_1c", "re_2", "re_3", "re_4", "re2_4", "re_5", "re_5_1", "re3_5", "porterStemmer", "w", "stem", "suffix", "firstch", "re", "re2", "re3", "re4", "fp", "stopWords", "words", "stopWord", "arr", "clause", "editDistance", "root", "stack", "frame", "noEditNode", "insertionNode", "substitutionNode", "charA", "charB", "transposeNode", "node", "final", "next", "edges", "edge", "labels", "qEdges", "qLen", "nEdges", "nLen", "q", "qEdge", "nEdge", "qNode", "word", "commonPrefix", "nextNode", "downTo", "childKey", "attrs", "queryString", "query", "parser", "matchingFields", "queryVectors", "termFieldCache", "requiredMatches", "prohibitedMatches", "terms", "clauseMatches", "m", "term", "termTokenSet", "expandedTerms", "field", "expandedTerm", "termIndex", "fieldPosting", "matchingDocumentRefs", "termField", "matchingDocumentsSet", "l", "matchingDocumentRef", "matchingFieldRef", "fieldMatch", "allRequiredMatches", "allProhibitedMatches", "matchingFieldRefs", "results", "matches", "fieldVector", "score", "docMatch", "match", "invertedIndex", "fieldVectors", "ref", "serializedIndex", "serializedVectors", "serializedInvertedIndex", "tokenSetBuilder", "tuple", "attributes", "number", "doc", "fields", "extractor", "fieldTerms", "metadataKey", "fieldRefs", "numberOfFields", "accumulator", "documentsWithField", "fieldRefsLength", "termIdfCache", "fieldLength", "termFrequencies", "termsLength", "fieldBoost", "docBoost", "tf", "idf", "scoreWithPrecision", "args", "clonedMetadata", "metadataKeys", "otherMatchData", "allFields", "options", "state", "subSlices", "type", "charCode", "lexer", "lexeme", "completedClause", "errorMessage", "nextLexeme", "possibleFields", "f", "boost", "factory", "import_lunr", "getElement", "selector", "node", "el", "getOptionalElement", "obj", "data", "key", "x", "y", "nodes", "parent", "i", "node", "setupSearchDocumentMap", "docs", "map", "doc", "path", "article", "split", "input", "separator", "fn", "_a", "match", "index", "until", "term", "extract", "input", "fn", "block", "start", "end", "stack", "highlight", "input", "table", "positions", "full", "highlightAll", "inputs", "mapping", "t", "prev", "next", "p", "q", "i", "cursor", "blocks", "a", "b", "index", "block", "group", "slices", "indexes", "start", "end", "length", "slice", "j", "tokenize", "input", "tokens", "inputs", "i", "table", "total", "extract", "block", "type", "start", "end", "_a", "section", "split", "index", "until", "subsection", "segments", "s", "l", "transform", "query", "fn", "term", "parts", "index", "prev", "next", "transformSearchQuery", "query", "transform", "part", "terms", "lexer", "type", "term", "start", "end", "split", "range", "parseSearchQuery", "value", "clause", "getSearchQueryTerms", "_a", "clauses", "result", "t", "segment", "query", "index", "segments", "wordcuts", "i", "j", "stack", "p", "q", "extractor", "table", "name", "doc", "id", "difference", "a", "b", "x", "y", "value", "Search", "config", "docs", "options", "field", "setupSearchDocumentMap", "tokenize", "fns", "lang", "language", "fn", "query", "segment", "transformSearchQuery", "clauses", "parseSearchQuery", "clause", "groups", "item", "ref", "score", "matchData", "__spreadValues", "terms", "getSearchQueryTerms", "positions", "match", "highlightAll", "highlight", "boost", "t", "__spreadProps", "__pow", "items", "result", "suggest", "titles", "builder", "index", "setupSearchLanguages", "config", "__async", "base", "worker", "getElement", "path", "scripts", "lang", "handler", "message", "Search", "query", "err", "lunr", "ev"]
}
diff --git a/assets/stylesheets/main.06209087.min.css b/assets/stylesheets/main.06209087.min.css
new file mode 100644
index 0000000..9bb1757
--- /dev/null
+++ b/assets/stylesheets/main.06209087.min.css
@@ -0,0 +1 @@
+@charset "UTF-8";html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;box-sizing:border-box}*,:after,:before{box-sizing:inherit}@media (prefers-reduced-motion){*,:after,:before{transition:none!important}}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{border:0;box-sizing:initial;display:block;height:.05rem;overflow:visible;padding:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{background:#0000;border:0;font-family:inherit;font-size:inherit;margin:0;padding:0}input{border:0;outline:none}:root{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3;--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:#526cfe1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-scheme=default]{color-scheme:light}[data-md-color-scheme=default] img[src$="#gh-dark-mode-only"],[data-md-color-scheme=default] img[src$="#only-dark"]{display:none}:root,[data-md-color-scheme=default]{--md-hue:225deg;--md-default-fg-color:#000000de;--md-default-fg-color--light:#0000008a;--md-default-fg-color--lighter:#00000052;--md-default-fg-color--lightest:#00000012;--md-default-bg-color:#fff;--md-default-bg-color--light:#ffffffb3;--md-default-bg-color--lighter:#ffffff4d;--md-default-bg-color--lightest:#ffffff1f;--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:#4287ff;--md-code-hl-color--light:#4287ff1a;--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-del-color:#f5503d26;--md-typeset-ins-color:#0bd57026;--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-mark-color:#ffff0080;--md-typeset-table-color:#0000001f;--md-typeset-table-color--light:rgba(0,0,0,.035);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-warning-fg-color:#000000de;--md-warning-bg-color:#ff9;--md-footer-fg-color:#fff;--md-footer-fg-color--light:#ffffffb3;--md-footer-fg-color--lighter:#ffffff73;--md-footer-bg-color:#000000de;--md-footer-bg-color--dark:#00000052;--md-shadow-z1:0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a;--md-shadow-z2:0 0.2rem 0.5rem #0000001a,0 0 0.05rem #00000040;--md-shadow-z3:0 0.2rem 0.5rem #0003,0 0 0.05rem #00000059}.md-icon svg{fill:currentcolor;display:block;height:1.2rem;width:1.2rem}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--md-text-font-family:var(--md-text-font,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;--md-code-font-family:var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,monospace}aside,body,input{font-feature-settings:"kern","liga";color:var(--md-typeset-color);font-family:var(--md-text-font-family)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family)}:root{--md-typeset-table-sort-icon:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--asc:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--desc:url('data:image/svg+xml;charset=utf-8,')}.md-typeset{-webkit-print-color-adjust:exact;color-adjust:exact;font-size:.8rem;line-height:1.6}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{margin-bottom:1em;margin-top:1em}.md-typeset h1{color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3;margin:0 0 1.25em}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{font-size:1.5625em;line-height:1.4;margin:1.6em 0 .64em}.md-typeset h3{font-size:1.25em;font-weight:400;letter-spacing:-.01em;line-height:1.5;margin:1.6em 0 .8em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{font-weight:700;letter-spacing:-.01em;margin:1em 0}.md-typeset h5,.md-typeset h6{color:var(--md-default-fg-color--light);font-size:.8em;font-weight:700;letter-spacing:-.01em;margin:1.25em 0}.md-typeset h5{text-transform:uppercase}.md-typeset hr{border-bottom:.05rem solid var(--md-default-fg-color--lightest);display:flow-root;margin:1.5em 0}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset a:focus code,.md-typeset a:hover code{background-color:var(--md-accent-fg-color--transparent)}.md-typeset a code{color:currentcolor;transition:background-color 125ms}.md-typeset a.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr;font-variant-ligatures:none}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone;font-size:.85em;padding:0 .2941176471em;word-break:break-word}.md-typeset code:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-typeset pre{display:flow-root;line-height:1.4;position:relative}.md-typeset pre>code{-webkit-box-decoration-break:slice;box-decoration-break:slice;box-shadow:none;display:block;margin:0;outline-color:var(--md-accent-fg-color);overflow:auto;padding:.7720588235em 1.1764705882em;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin;touch-action:auto;word-break:normal}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-typeset pre>code::-webkit-scrollbar{height:.2rem;width:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-typeset kbd{background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset;color:var(--md-default-fg-color);display:inline-block;font-size:.75em;padding:0 .6666666667em;vertical-align:text-top;word-break:break-word}.md-typeset mark{background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:inherit;word-break:break-word}.md-typeset abbr{border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help;text-decoration:none}.md-typeset small{opacity:.75}[dir=ltr] .md-typeset sub,[dir=ltr] .md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em}[dir=ltr] .md-typeset blockquote{padding-left:.6rem}[dir=rtl] .md-typeset blockquote{padding-right:.6rem}[dir=ltr] .md-typeset blockquote{border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{border-right:.2rem solid var(--md-default-fg-color--lighter)}.md-typeset blockquote{color:var(--md-default-fg-color--light);margin-left:0;margin-right:0}.md-typeset ul{list-style-type:disc}.md-typeset ul[type]{list-style-type:revert}[dir=ltr] .md-typeset ol,[dir=ltr] .md-typeset ul{margin-left:.625em}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em}.md-typeset ol,.md-typeset ul{padding:0}.md-typeset ol:not([hidden]),.md-typeset ul:not([hidden]){display:flow-root}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol[type],.md-typeset ul[type]{list-style-type:revert}[dir=ltr] .md-typeset ol li,[dir=ltr] .md-typeset ul li{margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}[dir=ltr] .md-typeset ol li ol,[dir=ltr] .md-typeset ol li ul,[dir=ltr] .md-typeset ul li ol,[dir=ltr] .md-typeset ul li ul{margin-left:.625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset dd{margin-left:1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em}.md-typeset dd{margin-bottom:1.5em;margin-top:1em}.md-typeset img,.md-typeset svg,.md-typeset video{height:auto;max-width:100%}.md-typeset img[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child{margin-top:0}.md-typeset figure{display:flow-root;margin:1em auto;max-width:100%;text-align:center;width:-moz-fit-content;width:fit-content}.md-typeset figure img{display:block;margin:0 auto}.md-typeset figcaption{font-style:italic;margin:1em auto;max-width:24rem}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){background-color:var(--md-default-bg-color);border:.05rem solid var(--md-typeset-table-color);border-radius:.1rem;display:inline-block;font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) td{border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) tbody tr{transition:background-color 125ms}.md-typeset table:not([class]) tbody tr:hover{background-color:var(--md-typeset-table-color--light);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}[dir=ltr] .md-typeset table th[role=columnheader]:after{margin-left:.5em}[dir=rtl] .md-typeset table th[role=columnheader]:after{margin-right:.5em}.md-typeset table th[role=columnheader]:after{content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-typeset-table-sort-icon);mask-image:var(--md-typeset-table-sort-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset table th[role=columnheader]:hover:after{background-color:var(--md-default-fg-color--lighter)}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--asc);mask-image:var(--md-typeset-table-sort-icon--asc)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--desc);mask-image:var(--md-typeset-table-sort-icon--desc)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;margin:0;overflow:hidden;width:100%}@media screen and (max-width:44.984375em){.md-content__inner>pre{margin:1em -.8rem}.md-content__inner>pre code{border-radius:0}}.md-typeset .md-author{border-radius:100%;display:block;flex-shrink:0;height:1.6rem;overflow:hidden;position:relative;transition:color 125ms,transform 125ms;width:1.6rem}.md-typeset .md-author img{display:block}.md-typeset .md-author--more{background:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--lighter);font-size:.6rem;font-weight:700;line-height:1.6rem;text-align:center}.md-typeset .md-author--long{height:2.4rem;width:2.4rem}.md-typeset a.md-author{transform:scale(1)}.md-typeset a.md-author img{border-radius:100%;filter:grayscale(100%) opacity(75%);transition:filter 125ms}.md-typeset a.md-author:focus,.md-typeset a.md-author:hover{transform:scale(1.1);z-index:1}.md-typeset a.md-author:focus img,.md-typeset a.md-author:hover img{filter:grayscale(0)}.md-banner{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color);overflow:auto}@media print{.md-banner{display:none}}.md-banner--warning{background-color:var(--md-warning-bg-color);color:var(--md-warning-fg-color)}.md-banner__inner{font-size:.7rem;margin:.6rem auto;padding:0 .8rem}[dir=ltr] .md-banner__button{float:right}[dir=rtl] .md-banner__button{float:left}.md-banner__button{color:inherit;cursor:pointer;transition:opacity .25s}.no-js .md-banner__button{display:none}.md-banner__button:hover{opacity:.7}html{font-size:125%;height:100%;overflow-x:hidden}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;font-size:.5rem;min-height:100%;position:relative;width:100%}@media print{body{display:block}}@media screen and (max-width:59.984375em){body[data-md-scrolllock]{position:fixed}}.md-grid{margin-left:auto;margin-right:auto;max-width:61rem}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;text-overflow:ellipsis}.md-toggle{display:none}.md-option{height:0;opacity:0;position:absolute;width:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-color:var(--md-accent-fg-color);outline-style:auto}.md-skip{background-color:var(--md-default-fg-color);border-radius:.1rem;color:var(--md-default-bg-color);font-size:.64rem;margin:.5rem;opacity:0;outline-color:var(--md-accent-fg-color);padding:.3rem .5rem;position:fixed;transform:translateY(.4rem);z-index:-1}.md-skip:focus{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms;z-index:10}@page{margin:25mm}:root{--md-clipboard-icon:url('data:image/svg+xml;charset=utf-8,')}.md-clipboard{border-radius:.1rem;color:var(--md-default-fg-color--lightest);cursor:pointer;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;position:absolute;right:.5em;top:.5em;transition:color .25s;width:1.5em;z-index:1}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .md-code__content{display:grid}@keyframes consent{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes overlay{0%{opacity:0}to{opacity:1}}.md-consent__overlay{animation:overlay .25s both;-webkit-backdrop-filter:blur(.1rem);backdrop-filter:blur(.1rem);background-color:#0000008a;height:100%;opacity:1;position:fixed;top:0;width:100%;z-index:5}.md-consent__inner{animation:consent .5s cubic-bezier(.1,.7,.1,1) both;background-color:var(--md-default-bg-color);border:0;border-radius:.1rem;bottom:0;box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;max-height:100%;overflow:auto;padding:0;position:fixed;width:100%;z-index:5}.md-consent__form{padding:.8rem}.md-consent__settings{display:none;margin:1em 0}input:checked+.md-consent__settings{display:block}.md-consent__controls{margin-bottom:.8rem}.md-typeset .md-consent__controls .md-button{display:inline}@media screen and (max-width:44.984375em){.md-typeset .md-consent__controls .md-button{display:block;margin-top:.4rem;text-align:center;width:100%}}.md-consent label{cursor:pointer}.md-content{flex-grow:1;min-width:0}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){[dir=ltr] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=ltr] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner,[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}}.md-content__inner:before{content:"";display:block;height:.4rem}.md-content__inner>:last-child{margin-bottom:0}[dir=ltr] .md-content__button{float:right}[dir=rtl] .md-content__button{float:left}[dir=ltr] .md-content__button{margin-left:.4rem}[dir=rtl] .md-content__button{margin-right:.4rem}.md-content__button{margin:.4rem 0;padding:0}@media print{.md-content__button{display:none}}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}[dir=ltr] .md-dialog{right:.8rem}[dir=rtl] .md-dialog{left:.8rem}.md-dialog{background-color:var(--md-default-fg-color);border-radius:.1rem;bottom:.8rem;box-shadow:var(--md-shadow-z3);min-width:11.1rem;opacity:0;padding:.4rem .6rem;pointer-events:none;position:fixed;transform:translateY(100%);transition:transform 0ms .4s,opacity .4s;z-index:4}@media print{.md-dialog{display:none}}.md-dialog--active{opacity:1;pointer-events:auto;transform:translateY(0);transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-feedback{margin:2em 0 1em;text-align:center}.md-feedback fieldset{border:none;margin:0;padding:0}.md-feedback__title{font-weight:700;margin:1em auto}.md-feedback__inner{position:relative}.md-feedback__list{display:flex;flex-wrap:wrap;place-content:baseline center;position:relative}.md-feedback__list:hover .md-icon:not(:disabled){color:var(--md-default-fg-color--lighter)}:disabled .md-feedback__list{min-height:1.8rem}.md-feedback__icon{color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;margin:0 .1rem;transition:color 125ms}.md-feedback__icon:not(:disabled).md-icon:hover{color:var(--md-accent-fg-color)}.md-feedback__icon:disabled{color:var(--md-default-fg-color--lightest);pointer-events:none}.md-feedback__note{opacity:0;position:relative;transform:translateY(.4rem);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-feedback__note>*{margin:0 auto;max-width:16rem}:disabled .md-feedback__note{opacity:1;transform:translateY(0)}.md-footer{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color)}@media print{.md-footer{display:none}}.md-footer__inner{justify-content:space-between;overflow:auto;padding:.2rem}.md-footer__inner:not([hidden]){display:flex}.md-footer__link{align-items:end;display:flex;flex-grow:0.01;margin-bottom:.4rem;margin-top:1rem;max-width:100%;outline-color:var(--md-accent-fg-color);overflow:hidden;transition:opacity .25s}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}[dir=rtl] .md-footer__link svg{transform:scaleX(-1)}@media screen and (max-width:44.984375em){.md-footer__link--prev{flex-shrink:0}.md-footer__link--prev .md-footer__title{display:none}}[dir=ltr] .md-footer__link--next{margin-left:auto}[dir=rtl] .md-footer__link--next{margin-right:auto}.md-footer__link--next{text-align:right}[dir=rtl] .md-footer__link--next{text-align:left}.md-footer__title{flex-grow:1;font-size:.9rem;margin-bottom:.7rem;max-width:calc(100% - 2.4rem);padding:0 1rem;white-space:nowrap}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-copyright{color:var(--md-footer-fg-color--lighter);font-size:.64rem;margin:auto .6rem;padding:.4rem 0;width:100%}@media screen and (min-width:45em){.md-copyright{width:auto}}.md-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-social{display:inline-flex;gap:.2rem;margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-social{padding:.6rem 0}}.md-social__link{display:inline-block;height:1.6rem;text-align:center;width:1.6rem}.md-social__link:before{line-height:1.9}.md-social__link svg{fill:currentcolor;max-height:.8rem;vertical-align:-25%}.md-typeset .md-button{border:.1rem solid;border-radius:.1rem;color:var(--md-primary-fg-color);cursor:pointer;display:inline-block;font-weight:700;padding:.625em 2em;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[dir=ltr] .md-typeset .md-input{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .md-input,[dir=rtl] .md-typeset .md-input{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .md-input{border-top-left-radius:.1rem}.md-typeset .md-input{border-bottom:.1rem solid var(--md-default-fg-color--lighter);box-shadow:var(--md-shadow-z1);font-size:.8rem;height:1.8rem;padding:0 .6rem;transition:border .25s,box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input--stretch{width:100%}.md-header{background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem #0000,0 .2rem .4rem #0000;color:var(--md-primary-bg-color);display:block;left:0;position:sticky;right:0;top:0;z-index:4}@media print{.md-header{display:none}}.md-header[hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header--shadow{box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header__inner{align-items:center;display:flex;padding:0 .2rem}.md-header__button{color:currentcolor;cursor:pointer;margin:.2rem;outline-color:var(--md-accent-fg-color);padding:.4rem;position:relative;transition:opacity .25s;vertical-align:middle;z-index:1}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.234375em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{fill:currentcolor;display:block;height:1.2rem;width:auto}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{display:flex;max-width:100%;position:absolute;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;white-space:nowrap}.md-header__topic+.md-header__topic{opacity:0;pointer-events:none;transform:translateX(1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__topic:first-child{font-weight:700}[dir=ltr] .md-header__title{margin-left:1rem;margin-right:.4rem}[dir=rtl] .md-header__title{margin-left:.4rem;margin-right:1rem}.md-header__title{flex-grow:1;font-size:.9rem;height:2.4rem;line-height:2.4rem}.md-header__title--active .md-header__topic{opacity:0;pointer-events:none;transform:translateX(-1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__title--active .md-header__topic{transform:translateX(1.25rem)}.md-header__title--active .md-header__topic+.md-header__topic{opacity:1;pointer-events:auto;transform:translateX(0);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;z-index:0}.md-header__title>.md-header__ellipsis{height:100%;position:relative;width:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;transition:max-width 0ms .25s,opacity .25s .25s;white-space:nowrap}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__option>input{bottom:0}.md-header__source{display:none}@media screen and (min-width:60em){[dir=ltr] .md-header__source{margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem}.md-header__source{display:block;max-width:11.7rem;width:11.7rem}}@media screen and (min-width:76.25em){[dir=ltr] .md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-meta{color:var(--md-default-fg-color--light);font-size:.7rem;line-height:1.3}.md-meta__list{display:inline-flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}.md-meta__item:not(:last-child):after{content:"·";margin-left:.2rem;margin-right:.2rem}.md-meta__link{color:var(--md-typeset-a-color)}.md-meta__link:focus,.md-meta__link:hover{color:var(--md-accent-fg-color)}.md-draft{background-color:#ff1744;border-radius:.125em;color:#fff;display:inline-block;font-weight:700;padding-left:.5714285714em;padding-right:.5714285714em}:root{--md-nav-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-nav-icon--next:url('data:image/svg+xml;charset=utf-8,');--md-toc-icon:url('data:image/svg+xml;charset=utf-8,')}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{color:var(--md-default-fg-color--light);display:block;font-weight:700;overflow:hidden;padding:0 .6rem;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{height:100%;width:auto}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__list{list-style:none;margin:0;padding:0}.md-nav__link{align-items:flex-start;display:flex;gap:.4rem;margin-top:.625em;scroll-snap-align:start;transition:color 125ms}.md-nav__link--passed{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active,.md-nav__item .md-nav__link--active code{color:var(--md-typeset-a-color)}.md-nav__link .md-ellipsis{position:relative}[dir=ltr] .md-nav__link .md-icon:last-child{margin-left:auto}[dir=rtl] .md-nav__link .md-icon:last-child{margin-right:auto}.md-nav__link svg{fill:currentcolor;flex-shrink:0;height:1.3em;position:relative}.md-nav__link[for]:focus,.md-nav__link[for]:hover,.md-nav__link[href]:focus,.md-nav__link[href]:hover{color:var(--md-accent-fg-color);cursor:pointer}.md-nav__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{background-color:currentcolor;display:block;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);width:100%}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__container>.md-nav__link{margin-top:0}.md-nav__container>.md-nav__link:first-child{flex-grow:1;min-width:0}.md-nav__icon{flex-shrink:0}.md-nav__source{display:none}@media screen and (max-width:76.234375em){.md-nav--primary,.md-nav--primary .md-nav{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;height:100%;left:0;position:absolute;right:0;top:0;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);cursor:pointer;height:5.6rem;line-height:2.4rem;padding:3rem .8rem .2rem;position:relative;white-space:nowrap}[dir=ltr] .md-nav--primary .md-nav__title .md-nav__icon{left:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem}.md-nav--primary .md-nav__title .md-nav__icon{display:block;height:1.2rem;margin:.2rem;position:absolute;top:.4rem;width:1.2rem}.md-nav--primary .md-nav__title .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}.md-nav--primary .md-nav__title~.md-nav__list{background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;overflow-y:auto;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);font-weight:700}.md-nav--primary .md-nav__title .md-logo{display:block;left:.2rem;margin:.2rem;padding:.4rem;position:absolute;right:.2rem;top:.2rem}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link svg{margin-top:.1em}.md-nav--primary .md-nav__link>.md-nav__link{padding:0}[dir=ltr] .md-nav--primary .md-nav__link .md-nav__icon{margin-right:-.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{margin-left:-.2rem}.md-nav--primary .md-nav__link .md-nav__icon{font-size:1.2rem;height:1.2rem;width:1.2rem}.md-nav--primary .md-nav__link .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav{background-color:initial;position:static}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;opacity:0;transform:translateX(100%);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{opacity:1;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.984375em){.md-nav--primary .md-nav__link[for=__toc]{display:flex}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{background-color:var(--md-primary-fg-color--dark);color:var(--md-primary-bg-color);display:block;padding:0 .2rem}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-nav--integrated .md-nav__link[for=__toc]{display:flex}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav{margin-bottom:-.4rem}.md-nav--secondary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--secondary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--secondary .md-nav__list{padding-right:.6rem}.md-nav--secondary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--secondary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--secondary .md-nav__item>.md-nav__link{margin-left:.4rem}}@media screen and (min-width:76.25em){.md-nav{margin-bottom:-.4rem;transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--primary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--primary .md-nav__list{padding-right:.6rem}.md-nav--primary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--primary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--primary .md-nav__item>.md-nav__link{margin-left:.4rem}.md-nav__toggle~.md-nav{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .25s,visibility 0ms .25s;visibility:collapse}.md-nav__toggle~.md-nav>.md-nav__list{overflow:hidden}.md-nav__toggle.md-toggle--indeterminate~.md-nav,.md-nav__toggle:checked~.md-nav{grid-template-rows:1fr;opacity:1;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .15s .1s,visibility 0ms;visibility:visible}.md-nav__toggle.md-toggle--indeterminate~.md-nav{transition:none}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{font-weight:700}.md-nav__item--section>.md-nav__link[for]{color:var(--md-default-fg-color--light)}.md-nav__item--section>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav__item--section>.md-nav__link .md-icon,.md-nav__item--section>.md-nav__link>[for]{display:none}[dir=ltr] .md-nav__item--section>.md-nav{margin-left:-.6rem}[dir=rtl] .md-nav__item--section>.md-nav{margin-right:-.6rem}.md-nav__item--section>.md-nav{display:block;opacity:1;visibility:visible}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{border-radius:100%;height:.9rem;transition:background-color .25s;width:.9rem}.md-nav__icon:hover{background-color:var(--md-accent-fg-color--transparent)}.md-nav__icon:after{background-color:currentcolor;border-radius:100%;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:transform .25s;vertical-align:-.1rem;width:100%}[dir=rtl] .md-nav__icon:after{transform:rotate(180deg)}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon:after,.md-nav__item--nested .md-toggle--indeterminate~.md-nav__link .md-nav__icon:after{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);margin-top:0;position:sticky;top:0;z-index:1}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active.md-nav__item--section{margin:0}[dir=ltr] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-left:-.6rem}[dir=rtl] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-right:-.6rem}.md-nav--lifted>.md-nav__list>.md-nav__item>[for]{color:var(--md-default-fg-color--light)}.md-nav--lifted .md-nav[data-md-level="1"]{grid-template-rows:1fr;opacity:1;visibility:visible}[dir=ltr] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-left:.05rem solid var(--md-primary-fg-color)}[dir=rtl] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-right:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{display:block;margin-bottom:1.25em;opacity:1;visibility:visible}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__list{overflow:visible;padding-bottom:0}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__title{display:none}}.md-pagination{font-size:.8rem;font-weight:700;gap:.4rem}.md-pagination,.md-pagination>*{align-items:center;display:flex;justify-content:center}.md-pagination>*{border-radius:.2rem;height:1.8rem;min-width:1.8rem;text-align:center}.md-pagination__current{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light)}.md-pagination__link{transition:color 125ms,background-color 125ms}.md-pagination__link:focus,.md-pagination__link:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-pagination__link:focus svg,.md-pagination__link:hover svg{color:var(--md-accent-fg-color)}.md-pagination__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-pagination__link svg{fill:currentcolor;color:var(--md-default-fg-color--lighter);display:block;max-height:100%;width:1.2rem}.md-post__back{border-bottom:.05rem solid var(--md-default-fg-color--lightest);margin-bottom:1.2rem;padding-bottom:1.2rem}@media screen and (max-width:76.234375em){.md-post__back{display:none}}[dir=rtl] .md-post__back svg{transform:scaleX(-1)}.md-post__authors{display:flex;flex-direction:column;gap:.6rem;margin:0 .6rem 1.2rem}.md-post .md-post__meta a{transition:color 125ms}.md-post .md-post__meta a:focus,.md-post .md-post__meta a:hover{color:var(--md-accent-fg-color)}.md-post__title{color:var(--md-default-fg-color--light);font-weight:700}.md-post--excerpt{margin-bottom:3.2rem}.md-post--excerpt .md-post__header{align-items:center;display:flex;gap:.6rem;min-height:1.6rem}.md-post--excerpt .md-post__authors{align-items:center;display:inline-flex;flex-direction:row;gap:.2rem;margin:0;min-height:2.4rem}[dir=ltr] .md-post--excerpt .md-post__meta .md-meta__list{margin-right:.4rem}[dir=rtl] .md-post--excerpt .md-post__meta .md-meta__list{margin-left:.4rem}.md-post--excerpt .md-post__content>:first-child{--md-scroll-margin:6rem;margin-top:0}.md-post>.md-nav--secondary{margin:1em 0}.md-profile{align-items:center;display:flex;font-size:.7rem;gap:.6rem;line-height:1.4;width:100%}.md-profile__description{flex-grow:1}.md-content--post{display:flex}@media screen and (max-width:76.234375em){.md-content--post{flex-flow:column-reverse}}.md-content--post>.md-content__inner{min-width:0}@media screen and (min-width:76.25em){[dir=ltr] .md-content--post>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-content--post>.md-content__inner{margin-right:1.2rem}}@media screen and (max-width:76.234375em){.md-sidebar.md-sidebar--post{padding:0;position:static;width:100%}.md-sidebar.md-sidebar--post .md-sidebar__scrollwrap{overflow:visible}.md-sidebar.md-sidebar--post .md-sidebar__inner{padding:0}.md-sidebar.md-sidebar--post .md-post__meta{margin-left:.6rem;margin-right:.6rem}.md-sidebar.md-sidebar--post .md-nav__item{border:none;display:inline}.md-sidebar.md-sidebar--post .md-nav__list{display:inline-flex;flex-wrap:wrap;gap:.6rem;padding-bottom:.6rem;padding-top:.6rem}.md-sidebar.md-sidebar--post .md-nav__link{padding:0}.md-sidebar.md-sidebar--post .md-nav{height:auto;margin-bottom:0;position:static}}:root{--md-progress-value:0;--md-progress-delay:400ms}.md-progress{background:var(--md-primary-bg-color);height:.075rem;opacity:min(clamp(0,var(--md-progress-value),1),clamp(0,100 - var(--md-progress-value),1));position:fixed;top:0;transform:scaleX(calc(var(--md-progress-value)*1%));transform-origin:left;transition:transform .5s cubic-bezier(.19,1,.22,1),opacity .25s var(--md-progress-delay);width:100%;z-index:4}:root{--md-search-result-icon:url('data:image/svg+xml;charset=utf-8,')}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__overlay{left:-2.2rem}[dir=rtl] .md-search__overlay{right:-2.2rem}.md-search__overlay{background-color:var(--md-default-bg-color);border-radius:1rem;height:2rem;overflow:hidden;pointer-events:none;position:absolute;top:-1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;width:2rem}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){[dir=ltr] .md-search__overlay{left:0}[dir=rtl] .md-search__overlay{right:0}.md-search__overlay{background-color:#0000008a;cursor:pointer;height:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0}[data-md-toggle=search]:checked~.md-header .md-search__overlay{height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@media screen and (max-width:29.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__inner{left:0}[dir=rtl] .md-search__inner{right:0}.md-search__inner{height:0;opacity:0;overflow:hidden;position:fixed;top:0;transform:translateX(5%);transition:width 0ms .3s,height 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;width:0;z-index:2}[dir=rtl] .md-search__inner{transform:translateX(-5%)}[data-md-toggle=search]:checked~.md-header .md-search__inner{height:100%;opacity:1;transform:translateX(0);transition:width 0ms 0ms,height 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__inner{float:right}[dir=rtl] .md-search__inner{float:left}.md-search__inner{padding:.1rem 0;position:relative;transition:width .25s cubic-bezier(.1,.7,.1,1);width:11.7rem}}@media screen and (min-width:60em) and (max-width:76.234375em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem #0000;height:2.4rem;position:relative;transition:color .25s,background-color .25s;z-index:2}@media screen and (min-width:60em){.md-search__form{background-color:#00000042;border-radius:.1rem;height:1.8rem}.md-search__form:hover{background-color:#ffffff1f}}[data-md-toggle=search]:checked~.md-header .md-search__form{background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0;box-shadow:0 0 .6rem #00000012;color:var(--md-default-fg-color)}[dir=ltr] .md-search__input{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__input{padding-left:2.2rem;padding-right:3.6rem}.md-search__input{background:#0000;font-size:.9rem;height:100%;position:relative;text-overflow:ellipsis;width:100%;z-index:2}.md-search__input::placeholder{transition:color .25s}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}@media screen and (max-width:59.984375em){.md-search__input{font-size:.9rem;height:2.4rem;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__input{padding-left:2.2rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input{color:inherit;font-size:.8rem}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}[data-md-toggle=search]:checked~.md-header .md-search__input{text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:#0000}}.md-search__icon{cursor:pointer;display:inline-block;height:1.2rem;transition:color .25s,opacity .25s;width:1.2rem}.md-search__icon:hover{opacity:.7}[dir=ltr] .md-search__icon[for=__search]{left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem}.md-search__icon[for=__search]{position:absolute;top:.3rem;z-index:2}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__icon[for=__search]{left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem}.md-search__icon[for=__search]{top:.6rem}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}[dir=ltr] .md-search__options{right:.5rem}[dir=rtl] .md-search__options{left:.5rem}.md-search__options{pointer-events:none;position:absolute;top:.3rem;z-index:2}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__options{right:.8rem}[dir=rtl] .md-search__options{left:.8rem}.md-search__options{top:.6rem}}[dir=ltr] .md-search__options>.md-icon{margin-left:.2rem}[dir=rtl] .md-search__options>.md-icon{margin-right:.2rem}.md-search__options>.md-icon{color:var(--md-default-fg-color--light);opacity:0;transform:scale(.75);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-search__options>.md-icon:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon{opacity:1;pointer-events:auto;transform:scale(1)}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon:hover{opacity:.7}[dir=ltr] .md-search__suggest{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__suggest{padding-left:2.2rem;padding-right:3.6rem}.md-search__suggest{align-items:center;color:var(--md-default-fg-color--lighter);display:flex;font-size:.9rem;height:100%;opacity:0;position:absolute;top:0;transition:opacity 50ms;white-space:nowrap;width:100%}@media screen and (min-width:60em){[dir=ltr] .md-search__suggest{padding-left:2.2rem}[dir=rtl] .md-search__suggest{padding-right:2.2rem}.md-search__suggest{font-size:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__suggest{opacity:1;transition:opacity .3s .1s}[dir=ltr] .md-search__output{border-bottom-left-radius:.1rem}[dir=ltr] .md-search__output,[dir=rtl] .md-search__output{border-bottom-right-radius:.1rem}[dir=rtl] .md-search__output{border-bottom-left-radius:.1rem}.md-search__output{overflow:hidden;position:absolute;width:100%;z-index:1}@media screen and (max-width:59.984375em){.md-search__output{bottom:0;top:2.4rem}}@media screen and (min-width:60em){.md-search__output{opacity:0;top:1.9rem;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:var(--md-shadow-z3);opacity:1}}.md-search__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);height:100%;overflow-y:auto;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1),(max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-search__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;padding:0 .8rem;scroll-snap-align:start}@media screen and (min-width:60em){[dir=ltr] .md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem}}.md-search-result__list{list-style:none;margin:0;padding:0;-webkit-user-select:none;user-select:none}.md-search-result__item{box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;scroll-snap-align:start;transition:background-color .25s}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more>summary{cursor:pointer;display:block;outline:none;position:sticky;scroll-snap-align:start;top:0;z-index:1}.md-search-result__more>summary::marker{display:none}.md-search-result__more>summary::-webkit-details-marker{display:none}.md-search-result__more>summary>div{color:var(--md-typeset-a-color);font-size:.64rem;padding:.75em .8rem;transition:color .25s,background-color .25s}@media screen and (min-width:60em){[dir=ltr] .md-search-result__more>summary>div{padding-left:2.2rem}[dir=rtl] .md-search-result__more>summary>div{padding-right:2.2rem}}.md-search-result__more>summary:focus>div,.md-search-result__more>summary:hover>div{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more[open]>summary{background-color:var(--md-default-bg-color)}.md-search-result__article{overflow:hidden;padding:0 .8rem;position:relative}@media screen and (min-width:60em){[dir=ltr] .md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem}}[dir=ltr] .md-search-result__icon{left:0}[dir=rtl] .md-search-result__icon{right:0}.md-search-result__icon{color:var(--md-default-fg-color--light);height:1.2rem;margin:.5rem;position:absolute;width:1.2rem}@media screen and (max-width:59.984375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result .md-typeset{color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6}.md-search-result .md-typeset h1{color:var(--md-default-fg-color);font-size:.8rem;font-weight:400;line-height:1.4;margin:.55rem 0}.md-search-result .md-typeset h1 mark{text-decoration:none}.md-search-result .md-typeset h2{color:var(--md-default-fg-color);font-size:.64rem;font-weight:700;line-height:1.6;margin:.5em 0}.md-search-result .md-typeset h2 mark{text-decoration:none}.md-search-result__terms{color:var(--md-default-fg-color);display:block;font-size:.64rem;font-style:italic;margin:.5em 0}.md-search-result mark{background-color:initial;color:var(--md-accent-fg-color);text-decoration:underline}.md-select{position:relative;z-index:1}.md-select__inner{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:50%;margin-top:.2rem;max-height:0;opacity:0;position:absolute;top:calc(100% - .2rem);transform:translate3d(-50%,.3rem,0);transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select__inner:after{border-bottom:.2rem solid #0000;border-bottom-color:var(--md-default-bg-color);border-left:.2rem solid #0000;border-right:.2rem solid #0000;border-top:0;content:"";height:0;left:50%;margin-left:-.2rem;margin-top:-.2rem;position:absolute;top:0;width:0}.md-select__list{border-radius:.1rem;font-size:.8rem;list-style-type:none;margin:0;max-height:inherit;overflow:auto;padding:0}.md-select__item{line-height:1.8rem}[dir=ltr] .md-select__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-select__link{padding-left:1.2rem;padding-right:.6rem}.md-select__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:background-color .25s,color .25s;width:100%}.md-select__link:focus,.md-select__link:hover{color:var(--md-accent-fg-color)}.md-select__link:focus{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{align-self:flex-start;flex-shrink:0;padding:1.2rem 0;position:sticky;top:2.4rem;width:12.1rem}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.234375em){[dir=ltr] .md-sidebar--primary{left:-12.1rem}[dir=rtl] .md-sidebar--primary{right:-12.1rem}.md-sidebar--primary{background-color:var(--md-default-bg-color);display:block;height:100%;position:fixed;top:0;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;width:12.1rem;z-index:5}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:var(--md-shadow-z3);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{bottom:0;left:0;margin:0;overflow:hidden;position:absolute;right:0;scroll-snap-type:none;top:0}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}.md-header--lifted~.md-container .md-sidebar{top:4.8rem}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{scrollbar-gutter:stable;-webkit-backface-visibility:hidden;backface-visibility:hidden;margin:0 .2rem;overflow-y:auto;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}.md-sidebar__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-sidebar__scrollwrap:focus-within,.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb:hover,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@supports selector(::-webkit-scrollbar){.md-sidebar__scrollwrap{scrollbar-gutter:auto}[dir=ltr] .md-sidebar__inner{padding-right:calc(100% - 11.5rem)}[dir=rtl] .md-sidebar__inner{padding-left:calc(100% - 11.5rem)}}@media screen and (max-width:76.234375em){.md-overlay{background-color:#0000008a;height:0;opacity:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0;z-index:5}[data-md-toggle=drawer]:checked~.md-overlay{height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@keyframes facts{0%{height:0}to{height:.65rem}}@keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}:root{--md-source-forks-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-repositories-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-stars-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-source{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.65rem;line-height:1.2;outline-color:var(--md-accent-fg-color);transition:opacity .25s;white-space:nowrap}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;height:2.4rem;vertical-align:middle;width:2rem}[dir=ltr] .md-source__icon svg{margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem}.md-source__icon svg{margin-top:.6rem}[dir=ltr] .md-source__icon+.md-source__repository{padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{padding-right:2rem}[dir=ltr] .md-source__icon+.md-source__repository{margin-left:-2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem}[dir=ltr] .md-source__repository{margin-left:.6rem}[dir=rtl] .md-source__repository{margin-right:.6rem}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{display:flex;font-size:.55rem;gap:.4rem;list-style-type:none;margin:.1rem 0 0;opacity:.75;overflow:hidden;padding:0;width:100%}.md-source__repository--active .md-source__facts{animation:facts .25s ease-in}.md-source__fact{overflow:hidden;text-overflow:ellipsis}.md-source__repository--active .md-source__fact{animation:fact .4s ease-out}[dir=ltr] .md-source__fact:before{margin-right:.1rem}[dir=rtl] .md-source__fact:before{margin-left:.1rem}.md-source__fact:before{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-top;width:.6rem}.md-source__fact:nth-child(1n+2){flex-shrink:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-source-file{margin:1em 0}[dir=ltr] .md-source-file__fact{margin-right:.6rem}[dir=rtl] .md-source-file__fact{margin-left:.6rem}.md-source-file__fact{align-items:center;color:var(--md-default-fg-color--light);display:inline-flex;font-size:.68rem;gap:.3rem}.md-source-file__fact .md-icon{flex-shrink:0;margin-bottom:.05rem}[dir=ltr] .md-source-file__fact .md-author{float:left}[dir=rtl] .md-source-file__fact .md-author{float:right}.md-source-file__fact .md-author{margin-right:.2rem}.md-source-file__fact svg{width:.9rem}:root{--md-status:url('data:image/svg+xml;charset=utf-8,');--md-status--new:url('data:image/svg+xml;charset=utf-8,');--md-status--deprecated:url('data:image/svg+xml;charset=utf-8,');--md-status--encrypted:url('data:image/svg+xml;charset=utf-8,')}.md-status:after{background-color:var(--md-default-fg-color--light);content:"";display:inline-block;height:1.125em;-webkit-mask-image:var(--md-status);mask-image:var(--md-status);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-bottom;width:1.125em}.md-status:hover:after{background-color:currentcolor}.md-status--new:after{-webkit-mask-image:var(--md-status--new);mask-image:var(--md-status--new)}.md-status--deprecated:after{-webkit-mask-image:var(--md-status--deprecated);mask-image:var(--md-status--deprecated)}.md-status--encrypted:after{-webkit-mask-image:var(--md-status--encrypted);mask-image:var(--md-status--encrypted)}.md-tabs{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);display:block;line-height:1.3;overflow:auto;width:100%;z-index:3}@media print{.md-tabs{display:none}}@media screen and (max-width:76.234375em){.md-tabs{display:none}}.md-tabs[hidden]{pointer-events:none}[dir=ltr] .md-tabs__list{margin-left:.2rem}[dir=rtl] .md-tabs__list{margin-right:.2rem}.md-tabs__list{contain:content;display:flex;list-style:none;margin:0;overflow:auto;padding:0;scrollbar-width:none;white-space:nowrap}.md-tabs__list::-webkit-scrollbar{display:none}.md-tabs__item{height:2.4rem;padding-left:.6rem;padding-right:.6rem}.md-tabs__item--active .md-tabs__link{color:inherit;opacity:1}.md-tabs__link{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:flex;font-size:.7rem;margin-top:.8rem;opacity:.7;outline-color:var(--md-accent-fg-color);outline-offset:.2rem;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}[dir=ltr] .md-tabs__link svg{margin-right:.4rem}[dir=rtl] .md-tabs__link svg{margin-left:.4rem}.md-tabs__link svg{fill:currentcolor;height:1.3em}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[hidden] .md-tabs__link{opacity:0;transform:translateY(50%);transition:transform 0ms .1s,opacity .1s}:root{--md-tag-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .md-tags:not([hidden]){display:inline-flex;flex-wrap:wrap;gap:.5em;margin-bottom:.75em;margin-top:-.125em}.md-typeset .md-tag{align-items:center;background:var(--md-default-fg-color--lightest);border-radius:2.4rem;display:inline-flex;font-size:.64rem;font-size:min(.8em,.64rem);font-weight:700;gap:.5em;letter-spacing:normal;line-height:1.6;padding:.3125em .78125em}.md-typeset .md-tag[href]{-webkit-tap-highlight-color:transparent;color:inherit;outline:none;transition:color 125ms,background-color 125ms}.md-typeset .md-tag[href]:focus,.md-typeset .md-tag[href]:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[id]>.md-typeset .md-tag{vertical-align:text-top}.md-typeset .md-tag-icon:before{background-color:var(--md-default-fg-color--lighter);content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-tag-icon);mask-image:var(--md-tag-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset .md-tag-icon[href]:focus:before,.md-typeset .md-tag-icon[href]:hover:before{background-color:var(--md-accent-bg-color)}@keyframes pulse{0%{transform:scale(.95)}75%{transform:scale(1)}to{transform:scale(.95)}}:root{--md-annotation-bg-icon:url('data:image/svg+xml;charset=utf-8,');--md-annotation-icon:url('data:image/svg+xml;charset=utf-8,')}.md-tooltip{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);font-family:var(--md-text-font-family);left:clamp(var(--md-tooltip-0,0rem) + .8rem,var(--md-tooltip-x),100vw + var(--md-tooltip-0,0rem) + .8rem - var(--md-tooltip-width) - 2 * .8rem);max-width:calc(100vw - 1.6rem);opacity:0;position:absolute;top:var(--md-tooltip-y);transform:translateY(-.4rem);transition:transform 0ms .25s,opacity .25s,z-index .25s;width:var(--md-tooltip-width);z-index:0}.md-tooltip--active{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip--inline{font-weight:700;-webkit-user-select:none;user-select:none;width:auto}.md-tooltip--inline:not(.md-tooltip--active){transform:translateY(.2rem) scale(.9)}.md-tooltip--inline .md-tooltip__inner{font-size:.5rem;padding:.2rem .4rem}[hidden]+.md-tooltip--inline{display:none}.focus-visible>.md-tooltip,.md-tooltip:target{outline:var(--md-accent-fg-color) auto}.md-tooltip__inner{font-size:.64rem;padding:.8rem}.md-tooltip__inner.md-typeset>:first-child{margin-top:0}.md-tooltip__inner.md-typeset>:last-child{margin-bottom:0}.md-annotation{font-style:normal;font-weight:400;outline:none;text-align:initial;vertical-align:text-bottom;white-space:normal}[dir=rtl] .md-annotation{direction:rtl}code .md-annotation{font-family:var(--md-code-font-family);font-size:inherit}.md-annotation:not([hidden]){display:inline-block;line-height:1.25}.md-annotation__index{border-radius:.01px;cursor:pointer;display:inline-block;margin-left:.4ch;margin-right:.4ch;outline:none;overflow:hidden;position:relative;-webkit-user-select:none;user-select:none;vertical-align:text-top;z-index:0}.md-annotation .md-annotation__index{transition:z-index .25s}@media screen{.md-annotation__index{width:2.2ch}[data-md-visible]>.md-annotation__index{animation:pulse 2s infinite}.md-annotation__index:before{background:var(--md-default-bg-color);-webkit-mask-image:var(--md-annotation-bg-icon);mask-image:var(--md-annotation-bg-icon)}.md-annotation__index:after,.md-annotation__index:before{content:"";height:2.2ch;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:-.1ch;width:2.2ch;z-index:-1}.md-annotation__index:after{background-color:var(--md-default-fg-color--lighter);-webkit-mask-image:var(--md-annotation-icon);mask-image:var(--md-annotation-icon);transform:scale(1.0001);transition:background-color .25s,transform .25s}.md-tooltip--active+.md-annotation__index:after{transform:rotate(45deg)}.md-tooltip--active+.md-annotation__index:after,:hover>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}}.md-tooltip--active+.md-annotation__index{animation-play-state:paused;transition-duration:0ms;z-index:2}.md-annotation__index [data-md-annotation-id]{display:inline-block}@media print{.md-annotation__index [data-md-annotation-id]{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);font-weight:700;padding:0 .6ch;white-space:nowrap}.md-annotation__index [data-md-annotation-id]:after{content:attr(data-md-annotation-id)}}.md-typeset .md-annotation-list{counter-reset:xxx;list-style:none}.md-typeset .md-annotation-list li{position:relative}[dir=ltr] .md-typeset .md-annotation-list li:before{left:-2.125em}[dir=rtl] .md-typeset .md-annotation-list li:before{right:-2.125em}.md-typeset .md-annotation-list li:before{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);content:counter(xxx);counter-increment:xxx;font-size:.8875em;font-weight:700;height:2ch;line-height:1.25;min-width:2ch;padding:0 .6ch;position:absolute;text-align:center;top:.25em}:root{--md-tooltip-width:20rem;--md-tooltip-tail:0.3rem}.md-tooltip2{-webkit-backface-visibility:hidden;backface-visibility:hidden;color:var(--md-default-fg-color);font-family:var(--md-text-font-family);opacity:0;pointer-events:none;position:absolute;top:calc(var(--md-tooltip-host-y) + var(--md-tooltip-y));transform:translateY(-.4rem);transform-origin:calc(var(--md-tooltip-host-x) + var(--md-tooltip-x)) 0;transition:transform 0ms .25s,opacity .25s,z-index .25s;width:100%;z-index:0}.md-tooltip2:before{border-left:var(--md-tooltip-tail) solid #0000;border-right:var(--md-tooltip-tail) solid #0000;content:"";display:block;left:clamp(1.5 * .8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-tail),100vw - 2 * var(--md-tooltip-tail) - 1.5 * .8rem);position:absolute;z-index:1}.md-tooltip2--top:before{border-top:var(--md-tooltip-tail) solid var(--md-default-bg-color);bottom:calc(var(--md-tooltip-tail)*-1 + .025rem);filter:drop-shadow(0 1px 0 hsla(0,0%,0%,.05))}.md-tooltip2--bottom:before{border-bottom:var(--md-tooltip-tail) solid var(--md-default-bg-color);filter:drop-shadow(0 -1px 0 hsla(0,0%,0%,.05));top:calc(var(--md-tooltip-tail)*-1 + .025rem)}.md-tooltip2--active{opacity:1;transform:translateY(0);transition:transform .4s cubic-bezier(0,1,.5,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip2__inner{scrollbar-gutter:stable;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);left:clamp(.8rem,var(--md-tooltip-host-x) - .8rem,100vw - var(--md-tooltip-width) - .8rem);max-height:40vh;max-width:calc(100vw - 1.6rem);position:relative;scrollbar-width:thin}.md-tooltip2__inner::-webkit-scrollbar{height:.2rem;width:.2rem}.md-tooltip2__inner::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-tooltip2__inner::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}[role=tooltip]>.md-tooltip2__inner{font-size:.5rem;font-weight:700;left:clamp(.8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-width)/2,100vw - var(--md-tooltip-width) - .8rem);max-width:min(100vw - 2 * .8rem,400px);padding:.2rem .4rem;-webkit-user-select:none;user-select:none;width:-moz-fit-content;width:fit-content}.md-tooltip2__inner.md-typeset>:first-child{margin-top:0}.md-tooltip2__inner.md-typeset>:last-child{margin-bottom:0}[dir=ltr] .md-top{margin-left:50%}[dir=rtl] .md-top{margin-right:50%}.md-top{background-color:var(--md-default-bg-color);border-radius:1.6rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color--light);cursor:pointer;display:block;font-size:.7rem;outline:none;padding:.4rem .8rem;position:fixed;top:3.2rem;transform:translate(-50%);transition:color 125ms,background-color 125ms,transform 125ms cubic-bezier(.4,0,.2,1),opacity 125ms;z-index:2}@media print{.md-top{display:none}}[dir=rtl] .md-top{transform:translate(50%)}.md-top[hidden]{opacity:0;pointer-events:none;transform:translate(-50%,.2rem);transition-duration:0ms}[dir=rtl] .md-top[hidden]{transform:translate(50%,.2rem)}.md-top:focus,.md-top:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top svg{display:inline-block;vertical-align:-.5em}@keyframes hoverfix{0%{pointer-events:none}}:root{--md-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-version{flex-shrink:0;font-size:.8rem;height:2.4rem}[dir=ltr] .md-version__current{margin-left:1.4rem;margin-right:.4rem}[dir=rtl] .md-version__current{margin-left:.4rem;margin-right:1.4rem}.md-version__current{color:inherit;cursor:pointer;outline:none;position:relative;top:.05rem}[dir=ltr] .md-version__current:after{margin-left:.4rem}[dir=rtl] .md-version__current:after{margin-right:.4rem}.md-version__current:after{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.4rem}.md-version__alias{margin-left:.3rem;opacity:.7}.md-version__list{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);list-style-type:none;margin:.2rem .8rem;max-height:0;opacity:0;overflow:auto;padding:0;position:absolute;scroll-snap-type:y mandatory;top:.15rem;transition:max-height 0ms .5s,opacity .25s .25s;z-index:3}.md-version:focus-within .md-version__list,.md-version:hover .md-version__list{max-height:10rem;opacity:1;transition:max-height 0ms,opacity .25s}@media (hover:none),(pointer:coarse){.md-version:hover .md-version__list{animation:hoverfix .25s forwards}.md-version:focus-within .md-version__list{animation:none}}.md-version__item{line-height:1.8rem}[dir=ltr] .md-version__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-version__link{padding-left:1.2rem;padding-right:.6rem}.md-version__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:color .25s,background-color .25s;white-space:nowrap;width:100%}.md-version__link:focus,.md-version__link:hover{color:var(--md-accent-fg-color)}.md-version__link:focus{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--abstract:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--info:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--tip:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--success:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--question:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--warning:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--failure:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--danger:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--bug:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--example:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--quote:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .admonition,.md-typeset details{background-color:var(--md-admonition-bg-color);border:.075rem solid #448aff;border-radius:.2rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid;transition:box-shadow 125ms}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}.md-typeset .admonition:focus-within,.md-typeset details:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .admonition>*,.md-typeset details>*{box-sizing:border-box}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-bottom:1em;margin-top:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{padding-left:2rem;padding-right:.6rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{padding-left:.6rem;padding-right:2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-left-width:.2rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-right-width:.2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset .admonition-title,.md-typeset summary{background-color:#448aff1a;border:none;font-weight:700;margin:0 -.6rem;padding-bottom:.4rem;padding-top:.4rem;position:relative}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}[dir=ltr] .md-typeset .admonition-title:before,[dir=ltr] .md-typeset summary:before{left:.6rem}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem}.md-typeset .admonition-title:before,.md-typeset summary:before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset .admonition-title code,.md-typeset summary code{box-shadow:0 0 0 .05rem var(--md-default-fg-color--lightest)}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .admonition.note:focus-within,.md-typeset details.note:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:#448aff1a}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note)}.md-typeset .note>.admonition-title:after,.md-typeset .note>summary:after{color:#448aff}.md-typeset .admonition.abstract,.md-typeset details.abstract{border-color:#00b0ff}.md-typeset .admonition.abstract:focus-within,.md-typeset details.abstract:focus-within{box-shadow:0 0 0 .2rem #00b0ff1a}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary{background-color:#00b0ff1a}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract)}.md-typeset .abstract>.admonition-title:after,.md-typeset .abstract>summary:after{color:#00b0ff}.md-typeset .admonition.info,.md-typeset details.info{border-color:#00b8d4}.md-typeset .admonition.info:focus-within,.md-typeset details.info:focus-within{box-shadow:0 0 0 .2rem #00b8d41a}.md-typeset .info>.admonition-title,.md-typeset .info>summary{background-color:#00b8d41a}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info)}.md-typeset .info>.admonition-title:after,.md-typeset .info>summary:after{color:#00b8d4}.md-typeset .admonition.tip,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .admonition.tip:focus-within,.md-typeset details.tip:focus-within{box-shadow:0 0 0 .2rem #00bfa51a}.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:#00bfa51a}.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip)}.md-typeset .tip>.admonition-title:after,.md-typeset .tip>summary:after{color:#00bfa5}.md-typeset .admonition.success,.md-typeset details.success{border-color:#00c853}.md-typeset .admonition.success:focus-within,.md-typeset details.success:focus-within{box-shadow:0 0 0 .2rem #00c8531a}.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:#00c8531a}.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success)}.md-typeset .success>.admonition-title:after,.md-typeset .success>summary:after{color:#00c853}.md-typeset .admonition.question,.md-typeset details.question{border-color:#64dd17}.md-typeset .admonition.question:focus-within,.md-typeset details.question:focus-within{box-shadow:0 0 0 .2rem #64dd171a}.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:#64dd171a}.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question)}.md-typeset .question>.admonition-title:after,.md-typeset .question>summary:after{color:#64dd17}.md-typeset .admonition.warning,.md-typeset details.warning{border-color:#ff9100}.md-typeset .admonition.warning:focus-within,.md-typeset details.warning:focus-within{box-shadow:0 0 0 .2rem #ff91001a}.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:#ff91001a}.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning)}.md-typeset .warning>.admonition-title:after,.md-typeset .warning>summary:after{color:#ff9100}.md-typeset .admonition.failure,.md-typeset details.failure{border-color:#ff5252}.md-typeset .admonition.failure:focus-within,.md-typeset details.failure:focus-within{box-shadow:0 0 0 .2rem #ff52521a}.md-typeset .failure>.admonition-title,.md-typeset .failure>summary{background-color:#ff52521a}.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure)}.md-typeset .failure>.admonition-title:after,.md-typeset .failure>summary:after{color:#ff5252}.md-typeset .admonition.danger,.md-typeset details.danger{border-color:#ff1744}.md-typeset .admonition.danger:focus-within,.md-typeset details.danger:focus-within{box-shadow:0 0 0 .2rem #ff17441a}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary{background-color:#ff17441a}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger)}.md-typeset .danger>.admonition-title:after,.md-typeset .danger>summary:after{color:#ff1744}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .admonition.bug:focus-within,.md-typeset details.bug:focus-within{box-shadow:0 0 0 .2rem #f500571a}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:#f500571a}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug)}.md-typeset .bug>.admonition-title:after,.md-typeset .bug>summary:after{color:#f50057}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .admonition.example:focus-within,.md-typeset details.example:focus-within{box-shadow:0 0 0 .2rem #7c4dff1a}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:#7c4dff1a}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example)}.md-typeset .example>.admonition-title:after,.md-typeset .example>summary:after{color:#7c4dff}.md-typeset .admonition.quote,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .admonition.quote:focus-within,.md-typeset details.quote:focus-within{box-shadow:0 0 0 .2rem #9e9e9e1a}.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:#9e9e9e1a}.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote)}.md-typeset .quote>.admonition-title:after,.md-typeset .quote>summary:after{color:#9e9e9e}:root{--md-footnotes-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}[dir=ltr] .md-typeset .footnote>ol{margin-left:0}[dir=rtl] .md-typeset .footnote>ol{margin-right:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:focus-within .footnote-backref{opacity:1;transform:translateX(0);transition:none}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-ref{font-size:.75em;font-weight:700}html .md-typeset .footnote-ref{outline-offset:.1rem}.md-typeset [id^="fnref:"]:target>.footnote-ref{outline:auto}.md-typeset .footnote-backref{color:var(--md-typeset-a-color);display:inline-block;font-size:0;opacity:0;transform:translateX(.25rem);transition:color .25s,transform .25s .25s,opacity 125ms .25s;vertical-align:text-bottom}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);opacity:1;transform:translateX(0)}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{background-color:currentcolor;content:"";display:inline-block;height:.8rem;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.8rem}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}[dir=ltr] .md-typeset .headerlink{margin-left:.5rem}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem}.md-typeset .headerlink{color:var(--md-default-fg-color--lighter);display:inline-block;opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{--md-scroll-margin:3.6rem;--md-scroll-offset:0rem;scroll-margin-top:calc(var(--md-scroll-margin) - var(--md-scroll-offset))}@media screen and (min-width:76.25em){.md-header--lifted~.md-container .md-typeset :target{--md-scroll-margin:6rem}}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{--md-scroll-offset:0.2rem}.md-typeset h4:target{--md-scroll-offset:0.15rem}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.984375em){.md-typeset div.arithmatex{margin:0 -.8rem}.md-typeset div.arithmatex>*{width:min-content}}.md-typeset div.arithmatex>*{margin-left:auto!important;margin-right:auto!important;padding:0 .8rem;touch-action:auto}.md-typeset div.arithmatex>* mjx-container{margin:0!important}.md-typeset div.arithmatex mjx-assistive-mml{height:0}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{-webkit-box-decoration-break:clone;box-decoration-break:clone;color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{box-shadow:none;display:block;margin:1em 0;overflow:auto;padding-left:.8rem;padding-right:.8rem}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset details{display:flow-root;overflow:visible;padding-top:0}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){box-shadow:none;padding-bottom:0}.md-typeset details:not([open])>summary{border-radius:.1rem}[dir=ltr] .md-typeset summary{padding-right:1.8rem}[dir=rtl] .md-typeset summary{padding-left:1.8rem}[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset summary{cursor:pointer;display:block;min-height:1rem;overflow:hidden}.md-typeset summary.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset summary:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[dir=ltr] .md-typeset summary:after{right:.4rem}[dir=rtl] .md-typeset summary:after{left:.4rem}.md-typeset summary:after{background-color:currentcolor;content:"";height:1rem;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;transform:rotate(0deg);transition:transform .25s;width:1rem}[dir=rtl] .md-typeset summary:after{transform:rotate(180deg)}.md-typeset summary::marker{display:none}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{--md-icon-size:1.125em;display:inline-flex;height:var(--md-icon-size);vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{fill:currentcolor;max-height:100%;width:var(--md-icon-size)}.md-typeset .lg,.md-typeset .xl,.md-typeset .xxl,.md-typeset .xxxl{vertical-align:text-bottom}.md-typeset .middle{vertical-align:middle}.md-typeset .lg{--md-icon-size:1.5em}.md-typeset .xl{--md-icon-size:2.25em}.md-typeset .xxl{--md-icon-size:3em}.md-typeset .xxxl{--md-icon-size:4em}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{background-color:var(--md-code-hl-color--light);box-shadow:2px 0 0 0 var(--md-code-hl-color) inset;display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em}.highlight span.filename{background-color:var(--md-code-bg-color);border-bottom:.05rem solid var(--md-default-fg-color--lightest);border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flow-root;font-size:.85em;font-weight:700;margin-top:1em;padding:.6617647059em 1.1764705882em;position:relative}.highlight span.filename+pre{margin-top:0}.highlight span.filename+pre>code{border-top-left-radius:0;border-top-right-radius:0}.highlight [data-linenos]:before{background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;color:var(--md-default-fg-color--light);content:attr(data-linenos);float:left;left:-1.1764705882em;margin-left:-1.1764705882em;margin-right:1.1764705882em;padding-left:1.1764705882em;position:sticky;-webkit-user-select:none;user-select:none;z-index:3}.highlight code a[id]{position:absolute;visibility:hidden}.highlight code[data-md-copying]{display:initial}.highlight code[data-md-copying] .hll{display:contents}.highlight code[data-md-copying] .md-annotation{display:none}.highlighttable{display:flow-root}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable th.filename{flex-grow:1;padding:0;text-align:left}.highlighttable th.filename span.filename{margin-top:0}.highlighttable .linenos{background-color:var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-top-left-radius:.1rem;font-size:.85em;padding:.7720588235em 0 .7720588235em 1.1764705882em;-webkit-user-select:none;user-select:none}.highlighttable .linenodiv{box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;padding-right:.5882352941em}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;min-width:0}.linenodiv a{color:inherit}.md-typeset .highlighttable{direction:ltr;margin:1em 0}.md-typeset .highlighttable>tbody>tr>.code>div>pre>code{border-bottom-left-radius:0;border-top-left-radius:0}.md-typeset .highlight+.result{border:.05rem solid var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top-width:.1rem;margin-top:-1.125em;overflow:visible;padding:0 1em}.md-typeset .highlight+.result:after{clear:both;content:"";display:block}@media screen and (max-width:44.984375em){.md-content__inner>.highlight{margin:1em -.8rem}.md-content__inner>.highlight>.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.code>div>pre>code,.md-content__inner>.highlight>.highlighttable>tbody>tr>.filename span.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.linenos,.md-content__inner>.highlight>pre>code{border-radius:0}.md-content__inner>.highlight+.result{border-left-width:0;border-radius:0;border-right-width:0;margin-left:-.8rem;margin-right:-.8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys span{color:var(--md-default-fg-color--light);padding:0 .2em}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{content:"⎇";padding-right:.4em}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{content:"⌘";padding-right:.4em}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{content:"⌃";padding-right:.4em}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{content:"◆";padding-right:.4em}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{content:"⌥";padding-right:.4em}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{content:"⇧";padding-right:.4em}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{content:"❖";padding-right:.4em}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{content:"⊞";padding-right:.4em}.md-typeset .keys .key-arrow-down:before{content:"↓";padding-right:.4em}.md-typeset .keys .key-arrow-left:before{content:"←";padding-right:.4em}.md-typeset .keys .key-arrow-right:before{content:"→";padding-right:.4em}.md-typeset .keys .key-arrow-up:before{content:"↑";padding-right:.4em}.md-typeset .keys .key-backspace:before{content:"⌫";padding-right:.4em}.md-typeset .keys .key-backtab:before{content:"⇤";padding-right:.4em}.md-typeset .keys .key-caps-lock:before{content:"⇪";padding-right:.4em}.md-typeset .keys .key-clear:before{content:"⌧";padding-right:.4em}.md-typeset .keys .key-context-menu:before{content:"☰";padding-right:.4em}.md-typeset .keys .key-delete:before{content:"⌦";padding-right:.4em}.md-typeset .keys .key-eject:before{content:"⏏";padding-right:.4em}.md-typeset .keys .key-end:before{content:"⤓";padding-right:.4em}.md-typeset .keys .key-escape:before{content:"⎋";padding-right:.4em}.md-typeset .keys .key-home:before{content:"⤒";padding-right:.4em}.md-typeset .keys .key-insert:before{content:"⎀";padding-right:.4em}.md-typeset .keys .key-page-down:before{content:"⇟";padding-right:.4em}.md-typeset .keys .key-page-up:before{content:"⇞";padding-right:.4em}.md-typeset .keys .key-print-screen:before{content:"⎙";padding-right:.4em}.md-typeset .keys .key-tab:after{content:"⇥";padding-left:.4em}.md-typeset .keys .key-num-enter:after{content:"⌤";padding-left:.4em}.md-typeset .keys .key-enter:after{content:"⏎";padding-left:.4em}:root{--md-tabbed-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-tabbed-icon--next:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .tabbed-set{border-radius:.1rem;display:flex;flex-flow:column wrap;margin:1em 0;position:relative}.md-typeset .tabbed-set>input{height:0;opacity:0;position:absolute;width:0}.md-typeset .tabbed-set>input:target{--md-scroll-offset:0.625em}.md-typeset .tabbed-set>input.focus-visible~.tabbed-labels:before{background-color:var(--md-accent-fg-color)}.md-typeset .tabbed-labels{-ms-overflow-style:none;box-shadow:0 -.05rem var(--md-default-fg-color--lightest) inset;display:flex;max-width:100%;overflow:auto;scrollbar-width:none}@media print{.md-typeset .tabbed-labels{display:contents}}@media screen{.js .md-typeset .tabbed-labels{position:relative}.js .md-typeset .tabbed-labels:before{background:var(--md-default-fg-color);bottom:0;content:"";display:block;height:2px;left:0;position:absolute;transform:translateX(var(--md-indicator-x));transition:width 225ms,background-color .25s,transform .25s;transition-timing-function:cubic-bezier(.4,0,.2,1);width:var(--md-indicator-width)}}.md-typeset .tabbed-labels::-webkit-scrollbar{display:none}.md-typeset .tabbed-labels>label{border-bottom:.1rem solid #0000;border-radius:.1rem .1rem 0 0;color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;font-size:.64rem;font-weight:700;padding:.78125em 1.25em .625em;scroll-margin-inline-start:1rem;transition:background-color .25s,color .25s;white-space:nowrap;width:auto}@media print{.md-typeset .tabbed-labels>label:first-child{order:1}.md-typeset .tabbed-labels>label:nth-child(2){order:2}.md-typeset .tabbed-labels>label:nth-child(3){order:3}.md-typeset .tabbed-labels>label:nth-child(4){order:4}.md-typeset .tabbed-labels>label:nth-child(5){order:5}.md-typeset .tabbed-labels>label:nth-child(6){order:6}.md-typeset .tabbed-labels>label:nth-child(7){order:7}.md-typeset .tabbed-labels>label:nth-child(8){order:8}.md-typeset .tabbed-labels>label:nth-child(9){order:9}.md-typeset .tabbed-labels>label:nth-child(10){order:10}.md-typeset .tabbed-labels>label:nth-child(11){order:11}.md-typeset .tabbed-labels>label:nth-child(12){order:12}.md-typeset .tabbed-labels>label:nth-child(13){order:13}.md-typeset .tabbed-labels>label:nth-child(14){order:14}.md-typeset .tabbed-labels>label:nth-child(15){order:15}.md-typeset .tabbed-labels>label:nth-child(16){order:16}.md-typeset .tabbed-labels>label:nth-child(17){order:17}.md-typeset .tabbed-labels>label:nth-child(18){order:18}.md-typeset .tabbed-labels>label:nth-child(19){order:19}.md-typeset .tabbed-labels>label:nth-child(20){order:20}}.md-typeset .tabbed-labels>label:hover{color:var(--md-default-fg-color)}.md-typeset .tabbed-labels>label>[href]:first-child{color:inherit}.md-typeset .tabbed-labels--linked>label{padding:0}.md-typeset .tabbed-labels--linked>label>a{display:block;padding:.78125em 1.25em .625em}.md-typeset .tabbed-content{width:100%}@media print{.md-typeset .tabbed-content{display:contents}}.md-typeset .tabbed-block{display:none}@media print{.md-typeset .tabbed-block{display:block}.md-typeset .tabbed-block:first-child{order:1}.md-typeset .tabbed-block:nth-child(2){order:2}.md-typeset .tabbed-block:nth-child(3){order:3}.md-typeset .tabbed-block:nth-child(4){order:4}.md-typeset .tabbed-block:nth-child(5){order:5}.md-typeset .tabbed-block:nth-child(6){order:6}.md-typeset .tabbed-block:nth-child(7){order:7}.md-typeset .tabbed-block:nth-child(8){order:8}.md-typeset .tabbed-block:nth-child(9){order:9}.md-typeset .tabbed-block:nth-child(10){order:10}.md-typeset .tabbed-block:nth-child(11){order:11}.md-typeset .tabbed-block:nth-child(12){order:12}.md-typeset .tabbed-block:nth-child(13){order:13}.md-typeset .tabbed-block:nth-child(14){order:14}.md-typeset .tabbed-block:nth-child(15){order:15}.md-typeset .tabbed-block:nth-child(16){order:16}.md-typeset .tabbed-block:nth-child(17){order:17}.md-typeset .tabbed-block:nth-child(18){order:18}.md-typeset .tabbed-block:nth-child(19){order:19}.md-typeset .tabbed-block:nth-child(20){order:20}}.md-typeset .tabbed-block>.highlight:first-child>pre,.md-typeset .tabbed-block>pre:first-child{margin:0}.md-typeset .tabbed-block>.highlight:first-child>pre>code,.md-typeset .tabbed-block>pre:first-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child>.filename{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable{margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.filename span.filename,.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.linenos{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.code>div>pre>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child+.result{margin-top:-.125em}.md-typeset .tabbed-block>.tabbed-set{margin:0}.md-typeset .tabbed-button{align-self:center;border-radius:100%;color:var(--md-default-fg-color--light);cursor:pointer;display:block;height:.9rem;margin-top:.1rem;pointer-events:auto;transition:background-color .25s;width:.9rem}.md-typeset .tabbed-button:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .tabbed-button:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-tabbed-icon--prev);mask-image:var(--md-tabbed-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color .25s,transform .25s;width:100%}.md-typeset .tabbed-control{background:linear-gradient(to right,var(--md-default-bg-color) 60%,#0000);display:flex;height:1.9rem;justify-content:start;pointer-events:none;position:absolute;transition:opacity 125ms;width:1.2rem}[dir=rtl] .md-typeset .tabbed-control{transform:rotate(180deg)}.md-typeset .tabbed-control[hidden]{opacity:0}.md-typeset .tabbed-control--next{background:linear-gradient(to left,var(--md-default-bg-color) 60%,#0000);justify-content:end;right:0}.md-typeset .tabbed-control--next .tabbed-button:after{-webkit-mask-image:var(--md-tabbed-icon--next);mask-image:var(--md-tabbed-icon--next)}@media screen and (max-width:44.984375em){[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels{margin:0 -.8rem;max-width:100vw;scroll-padding-inline-start:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels:after{content:""}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-right:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-left:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-right:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{width:2rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-left:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-right:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-left:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{width:2rem}}@media screen{.md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){color:var(--md-default-fg-color)}.md-typeset .no-js .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .no-js .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .no-js .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .no-js .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .no-js .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .no-js .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .no-js .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .no-js .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .no-js .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .no-js .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .no-js .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .no-js .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .no-js .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .no-js .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .no-js .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .no-js .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .no-js .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .no-js .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .no-js .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .no-js .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.no-js .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.no-js .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.no-js .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.no-js .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.no-js .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.no-js .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.no-js .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.no-js .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.no-js .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.no-js .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.no-js .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.no-js .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.no-js .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.no-js .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.no-js .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.no-js .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.no-js .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.no-js .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.no-js .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.no-js .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){border-color:var(--md-default-fg-color)}}.md-typeset .tabbed-set>input:first-child.focus-visible~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10).focus-visible~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11).focus-visible~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12).focus-visible~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13).focus-visible~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14).focus-visible~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15).focus-visible~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16).focus-visible~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17).focus-visible~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18).focus-visible~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19).focus-visible~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2).focus-visible~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20).focus-visible~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3).focus-visible~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4).focus-visible~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5).focus-visible~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6).focus-visible~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7).focus-visible~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8).focus-visible~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9).focus-visible~.tabbed-labels>:nth-child(9){color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:first-child:checked~.tabbed-content>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-content>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-content>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-content>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-content>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-content>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-content>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-content>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-content>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-content>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-content>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-content>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-content>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-content>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-content>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-content>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-content>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-content>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-content>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-content>:nth-child(9){display:block}:root{--md-tasklist-icon:url('data:image/svg+xml;charset=utf-8,');--md-tasklist-icon--checked:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .task-list-item{list-style-type:none;position:relative}[dir=ltr] .md-typeset .task-list-item [type=checkbox]{left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}[dir=ltr] .md-typeset .task-list-indicator:before{left:-1.5em}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em}.md-typeset .task-list-indicator:before{background-color:var(--md-default-fg-color--lightest);content:"";height:1.25em;-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.15em;width:1.25em}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}:root>*{--md-mermaid-font-family:var(--md-text-font-family),sans-serif;--md-mermaid-edge-color:var(--md-code-fg-color);--md-mermaid-node-bg-color:var(--md-accent-fg-color--transparent);--md-mermaid-node-fg-color:var(--md-accent-fg-color);--md-mermaid-label-bg-color:var(--md-default-bg-color);--md-mermaid-label-fg-color:var(--md-code-fg-color);--md-mermaid-sequence-actor-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actor-fg-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-actor-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-actor-line-color:var(--md-default-fg-color--lighter);--md-mermaid-sequence-actorman-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actorman-line-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-box-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-box-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-label-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-label-fg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-loop-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-loop-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-loop-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-message-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-message-line-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-note-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-border-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-number-bg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-number-fg-color:var(--md-accent-bg-color)}.mermaid{line-height:normal;margin:1em 0}.md-typeset .grid{grid-gap:.4rem;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));margin:1em 0}.md-typeset .grid.cards>ol,.md-typeset .grid.cards>ul{display:contents}.md-typeset .grid.cards>ol>li,.md-typeset .grid.cards>ul>li,.md-typeset .grid>.card{border:.05rem solid var(--md-default-fg-color--lightest);border-radius:.1rem;display:block;margin:0;padding:.8rem;transition:border .25s,box-shadow .25s}.md-typeset .grid.cards>ol>li:focus-within,.md-typeset .grid.cards>ol>li:hover,.md-typeset .grid.cards>ul>li:focus-within,.md-typeset .grid.cards>ul>li:hover,.md-typeset .grid>.card:focus-within,.md-typeset .grid>.card:hover{border-color:#0000;box-shadow:var(--md-shadow-z2)}.md-typeset .grid.cards>ol>li>hr,.md-typeset .grid.cards>ul>li>hr,.md-typeset .grid>.card>hr{margin-bottom:1em;margin-top:1em}.md-typeset .grid.cards>ol>li>:first-child,.md-typeset .grid.cards>ul>li>:first-child,.md-typeset .grid>.card>:first-child{margin-top:0}.md-typeset .grid.cards>ol>li>:last-child,.md-typeset .grid.cards>ul>li>:last-child,.md-typeset .grid>.card>:last-child{margin-bottom:0}.md-typeset .grid>*,.md-typeset .grid>.admonition,.md-typeset .grid>.highlight>*,.md-typeset .grid>.highlighttable,.md-typeset .grid>.md-typeset details,.md-typeset .grid>details,.md-typeset .grid>pre{margin-bottom:0;margin-top:0}.md-typeset .grid>.highlight>pre:only-child,.md-typeset .grid>.highlight>pre>code,.md-typeset .grid>.highlighttable,.md-typeset .grid>.highlighttable>tbody,.md-typeset .grid>.highlighttable>tbody>tr,.md-typeset .grid>.highlighttable>tbody>tr>.code,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre>code{height:100%}.md-typeset .grid>.tabbed-set{margin-bottom:0;margin-top:0}@media screen and (min-width:45em){[dir=ltr] .md-typeset .inline{float:left}[dir=rtl] .md-typeset .inline{float:right}[dir=ltr] .md-typeset .inline{margin-right:.8rem}[dir=rtl] .md-typeset .inline{margin-left:.8rem}.md-typeset .inline{margin-bottom:.8rem;margin-top:0;width:11.7rem}[dir=ltr] .md-typeset .inline.end{float:right}[dir=rtl] .md-typeset .inline.end{float:left}[dir=ltr] .md-typeset .inline.end{margin-left:.8rem;margin-right:0}[dir=rtl] .md-typeset .inline.end{margin-left:0;margin-right:.8rem}}
\ No newline at end of file
diff --git a/assets/stylesheets/main.06209087.min.css.map b/assets/stylesheets/main.06209087.min.css.map
new file mode 100644
index 0000000..a685c98
--- /dev/null
+++ b/assets/stylesheets/main.06209087.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["src/templates/assets/stylesheets/main/components/_meta.scss","../../../../src/templates/assets/stylesheets/main.scss","src/templates/assets/stylesheets/main/_resets.scss","src/templates/assets/stylesheets/main/_colors.scss","src/templates/assets/stylesheets/main/_icons.scss","src/templates/assets/stylesheets/main/_typeset.scss","src/templates/assets/stylesheets/utilities/_break.scss","src/templates/assets/stylesheets/main/components/_author.scss","src/templates/assets/stylesheets/main/components/_banner.scss","src/templates/assets/stylesheets/main/components/_base.scss","src/templates/assets/stylesheets/main/components/_clipboard.scss","src/templates/assets/stylesheets/main/components/_code.scss","src/templates/assets/stylesheets/main/components/_consent.scss","src/templates/assets/stylesheets/main/components/_content.scss","src/templates/assets/stylesheets/main/components/_dialog.scss","src/templates/assets/stylesheets/main/components/_feedback.scss","src/templates/assets/stylesheets/main/components/_footer.scss","src/templates/assets/stylesheets/main/components/_form.scss","src/templates/assets/stylesheets/main/components/_header.scss","node_modules/material-design-color/material-color.scss","src/templates/assets/stylesheets/main/components/_nav.scss","src/templates/assets/stylesheets/main/components/_pagination.scss","src/templates/assets/stylesheets/main/components/_post.scss","src/templates/assets/stylesheets/main/components/_progress.scss","src/templates/assets/stylesheets/main/components/_search.scss","src/templates/assets/stylesheets/main/components/_select.scss","src/templates/assets/stylesheets/main/components/_sidebar.scss","src/templates/assets/stylesheets/main/components/_source.scss","src/templates/assets/stylesheets/main/components/_status.scss","src/templates/assets/stylesheets/main/components/_tabs.scss","src/templates/assets/stylesheets/main/components/_tag.scss","src/templates/assets/stylesheets/main/components/_tooltip.scss","src/templates/assets/stylesheets/main/components/_tooltip2.scss","src/templates/assets/stylesheets/main/components/_top.scss","src/templates/assets/stylesheets/main/components/_version.scss","src/templates/assets/stylesheets/main/extensions/markdown/_admonition.scss","src/templates/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/templates/assets/stylesheets/main/extensions/markdown/_toc.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/templates/assets/stylesheets/main/integrations/_mermaid.scss","src/templates/assets/stylesheets/main/modifiers/_grid.scss","src/templates/assets/stylesheets/main/modifiers/_inline.scss"],"names":[],"mappings":"AA0CE,gBC4xCF,CC1yCA,KAEE,6BAAA,CAAA,0BAAA,CAAA,qBAAA,CADA,qBDzBF,CC8BA,iBAGE,kBD3BF,CC8BE,gCANF,iBAOI,yBDzBF,CACF,CC6BA,KACE,QD1BF,CC8BA,qBAIE,uCD3BF,CC+BA,EACE,aAAA,CACA,oBD5BF,CCgCA,GAME,QAAA,CALA,kBAAA,CACA,aAAA,CACA,aAAA,CAEA,gBAAA,CADA,SD3BF,CCiCA,MACE,aD9BF,CCkCA,QAEE,eD/BF,CCmCA,IACE,iBDhCF,CCoCA,MAEE,uBAAA,CADA,gBDhCF,CCqCA,MAEE,eAAA,CACA,kBDlCF,CCsCA,OAKE,gBAAA,CACA,QAAA,CAHA,mBAAA,CACA,iBAAA,CAFA,QAAA,CADA,SD9BF,CCuCA,MACE,QAAA,CACA,YDpCF,CErDA,MAIE,6BAAA,CACA,oCAAA,CACA,mCAAA,CACA,0BAAA,CACA,sCAAA,CAGA,4BAAA,CACA,2CAAA,CACA,yBAAA,CACA,qCFmDF,CE7CA,+BAIE,kBF6CF,CE1CE,oHAEE,YF4CJ,CEnCA,qCAIE,eAAA,CAGA,+BAAA,CACA,sCAAA,CACA,wCAAA,CACA,yCAAA,CACA,0BAAA,CACA,sCAAA,CACA,wCAAA,CACA,yCAAA,CAGA,0BAAA,CACA,0BAAA,CAGA,0BAAA,CACA,mCAAA,CAGA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CAGA,+CAAA,CAGA,gCAAA,CACA,gCAAA,CAGA,8BAAA,CACA,kCAAA,CACA,qCAAA,CAGA,iCAAA,CAGA,kCAAA,CACA,gDAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,+BAAA,CACA,0BAAA,CAGA,yBAAA,CACA,qCAAA,CACA,uCAAA,CACA,8BAAA,CACA,oCAAA,CAGA,8DAAA,CAKA,8DAAA,CAKA,0DFKF,CG9HE,aAIE,iBAAA,CAHA,aAAA,CAEA,aAAA,CADA,YHmIJ,CIxIA,KACE,kCAAA,CACA,iCAAA,CAGA,uGAAA,CAKA,mFJyIF,CInIA,iBAIE,mCAAA,CACA,6BAAA,CAFA,sCJwIF,CIlIA,aAIE,4BAAA,CADA,sCJsIF,CI7HA,MACE,wNAAA,CACA,gNAAA,CACA,iNJgIF,CIzHA,YAGE,gCAAA,CAAA,kBAAA,CAFA,eAAA,CACA,eJ6HF,CIxHE,aAPF,YAQI,gBJ2HF,CACF,CIxHE,uGAME,iBAAA,CAAA,cJ0HJ,CItHE,eAKE,uCAAA,CAHA,aAAA,CAEA,eAAA,CAHA,iBJ6HJ,CIpHE,8BAPE,eAAA,CAGA,qBJ+HJ,CI3HE,eAEE,kBAAA,CAEA,eAAA,CAHA,oBJ0HJ,CIlHE,eAEE,gBAAA,CACA,eAAA,CAEA,qBAAA,CADA,eAAA,CAHA,mBJwHJ,CIhHE,kBACE,eJkHJ,CI9GE,eAEE,eAAA,CACA,qBAAA,CAFA,YJkHJ,CI5GE,8BAKE,uCAAA,CAFA,cAAA,CACA,eAAA,CAEA,qBAAA,CAJA,eJkHJ,CI1GE,eACE,wBJ4GJ,CIxGE,eAGE,+DAAA,CAFA,iBAAA,CACA,cJ2GJ,CItGE,cACE,+BAAA,CACA,qBJwGJ,CIrGI,mCAEE,sBJsGN,CIlGI,wCACE,+BJoGN,CIjGM,kDACE,uDJmGR,CI9FI,mBACE,kBAAA,CACA,iCJgGN,CI5FI,4BACE,uCAAA,CACA,oBJ8FN,CIzFE,iDAIE,6BAAA,CACA,aAAA,CAFA,2BJ6FJ,CIxFI,aARF,iDASI,oBJ6FJ,CACF,CIzFE,iBAIE,wCAAA,CACA,mBAAA,CACA,kCAAA,CAAA,0BAAA,CAJA,eAAA,CADA,uBAAA,CAEA,qBJ8FJ,CIxFI,qCAEE,uCAAA,CADA,YJ2FN,CIrFE,gBAEE,iBAAA,CACA,eAAA,CAFA,iBJyFJ,CIpFI,qBAWE,kCAAA,CAAA,0BAAA,CADA,eAAA,CATA,aAAA,CAEA,QAAA,CAMA,uCAAA,CALA,aAAA,CAFA,oCAAA,CAKA,yDAAA,CACA,oBAAA,CAFA,iBAAA,CADA,iBJ4FN,CInFM,2BACE,+CJqFR,CIjFM,wCAEE,YAAA,CADA,WJoFR,CI/EM,8CACE,oDJiFR,CI9EQ,oDACE,0CJgFV,CIzEE,gBAOE,4CAAA,CACA,mBAAA,CACA,mKACE,CANF,gCAAA,CAHA,oBAAA,CAEA,eAAA,CADA,uBAAA,CAIA,uBAAA,CADA,qBJ+EJ,CIpEE,iBAGE,6CAAA,CACA,kCAAA,CAAA,0BAAA,CAHA,aAAA,CACA,qBJwEJ,CIlEE,iBAGE,6DAAA,CADA,WAAA,CADA,oBJsEJ,CIhEE,kBACE,WJkEJ,CI9DE,oDAEE,qBJgEJ,CIlEE,oDAEE,sBJgEJ,CI5DE,iCACE,kBJiEJ,CIlEE,iCACE,mBJiEJ,CIlEE,iCAIE,2DJ8DJ,CIlEE,iCAIE,4DJ8DJ,CIlEE,uBAGE,uCAAA,CADA,aAAA,CAAA,cJgEJ,CI1DE,eACE,oBJ4DJ,CIxDI,qBACE,sBJ0DN,CIrDE,kDAGE,kBJuDJ,CI1DE,kDAGE,mBJuDJ,CI1DE,8BAEE,SJwDJ,CIpDI,0DACE,iBJuDN,CInDI,oCACE,2BJsDN,CInDM,0CACE,2BJsDR,CIhDI,0CACE,sBJmDN,CI/CI,wDACE,kBJmDN,CIpDI,wDACE,mBJmDN,CIpDI,oCAEE,kBJkDN,CI/CM,kGAEE,aJmDR,CI/CM,0DACE,eJkDR,CI9CM,4HAEE,kBJiDR,CInDM,4HAEE,mBJiDR,CInDM,oFACE,kBAAA,CAAA,eJkDR,CI3CE,yBAEE,mBJ6CJ,CI/CE,yBAEE,oBJ6CJ,CI/CE,eACE,mBAAA,CAAA,cJ8CJ,CIzCE,kDAIE,WAAA,CADA,cJ4CJ,CIpCI,4BAEE,oBJsCN,CIlCI,6BAEE,oBJoCN,CIhCI,kCACE,YJkCN,CI7BE,mBACE,iBAAA,CAGA,eAAA,CADA,cAAA,CAEA,iBAAA,CAHA,sBAAA,CAAA,iBJkCJ,CI5BI,uBACE,aAAA,CACA,aJ8BN,CIzBE,uBAGE,iBAAA,CADA,eAAA,CADA,eJ6BJ,CIvBE,mBACE,cJyBJ,CIrBE,+BAME,2CAAA,CACA,iDAAA,CACA,mBAAA,CAPA,oBAAA,CAGA,gBAAA,CAFA,cAAA,CACA,aAAA,CAEA,iBJ0BJ,CIpBI,aAXF,+BAYI,aJuBJ,CACF,CIlBI,iCACE,gBJoBN,CIbM,8FACE,YJeR,CIXM,4FACE,eJaR,CIRI,8FACE,eJUN,CIPM,kHACE,gBJSR,CIJI,kCAGE,eAAA,CAFA,cAAA,CACA,sBAAA,CAEA,kBJMN,CIFI,kCAGE,qDAAA,CAFA,sBAAA,CACA,kBJKN,CIAI,wCACE,iCJEN,CICM,8CACE,qDAAA,CACA,sDJCR,CIII,iCACE,iBJFN,CIOE,wCACE,cJLJ,CIQI,wDAIE,gBJAN,CIJI,wDAIE,iBJAN,CIJI,8CAME,UAAA,CALA,oBAAA,CAEA,YAAA,CAIA,oDAAA,CAAA,4CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,iCAAA,CALA,0BAAA,CAHA,WJEN,CIUI,oDACE,oDJRN,CIYI,mEACE,kDAAA,CACA,yDAAA,CAAA,iDJVN,CIcI,oEACE,kDAAA,CACA,0DAAA,CAAA,kDJZN,CIiBE,wBACE,iBAAA,CACA,eAAA,CACA,iBJfJ,CImBE,mBACE,oBAAA,CAEA,kBAAA,CADA,eJhBJ,CIoBI,aANF,mBAOI,aJjBJ,CACF,CIoBI,8BACE,aAAA,CAEA,QAAA,CACA,eAAA,CAFA,UJhBN,CK1VI,0CDyXF,uBACE,iBJ3BF,CI8BE,4BACE,eJ5BJ,CACF,CMzhBE,uBAOE,kBAAA,CALA,aAAA,CACA,aAAA,CAEA,aAAA,CACA,eAAA,CALA,iBAAA,CAOA,sCACE,CALF,YN+hBJ,CMthBI,2BACE,aNwhBN,CMphBI,6BAME,+CAAA,CAFA,yCAAA,CAHA,eAAA,CACA,eAAA,CACA,kBAAA,CAEA,iBNuhBN,CMlhBI,6BAEE,aAAA,CADA,YNqhBN,CM/gBE,wBACE,kBNihBJ,CM9gBI,4BAIE,kBAAA,CAHA,mCAAA,CAIA,uBN8gBN,CM1gBI,4DAEE,oBAAA,CADA,SN6gBN,CMzgBM,oEACE,mBN2gBR,COpkBA,WAGE,0CAAA,CADA,+BAAA,CADA,aPykBF,COpkBE,aANF,WAOI,YPukBF,CACF,COpkBE,oBAEE,2CAAA,CADA,gCPukBJ,COlkBE,kBAGE,eAAA,CADA,iBAAA,CADA,ePskBJ,COhkBE,6BACE,WPqkBJ,COtkBE,6BACE,UPqkBJ,COtkBE,mBAEE,aAAA,CACA,cAAA,CACA,uBPkkBJ,CO/jBI,0BACE,YPikBN,CO7jBI,yBACE,UP+jBN,CQpmBA,KASE,cAAA,CARA,WAAA,CACA,iBRwmBF,CKpcI,oCGtKJ,KAaI,gBRimBF,CACF,CKzcI,oCGtKJ,KAkBI,cRimBF,CACF,CQ5lBA,KASE,2CAAA,CAPA,YAAA,CACA,qBAAA,CAKA,eAAA,CAHA,eAAA,CAJA,iBAAA,CAGA,URkmBF,CQ1lBE,aAZF,KAaI,aR6lBF,CACF,CK1cI,0CGhJF,yBAII,cR0lBJ,CACF,CQjlBA,SAEE,gBAAA,CAAA,iBAAA,CADA,eRqlBF,CQhlBA,cACE,YAAA,CACA,qBAAA,CACA,WRmlBF,CQhlBE,aANF,cAOI,aRmlBF,CACF,CQ/kBA,SACE,WRklBF,CQ/kBE,gBACE,YAAA,CACA,WAAA,CACA,iBRilBJ,CQ5kBA,aACE,eAAA,CACA,sBR+kBF,CQtkBA,WACE,YRykBF,CQpkBA,WAGE,QAAA,CACA,SAAA,CAHA,iBAAA,CACA,ORykBF,CQpkBE,uCACE,aRskBJ,CQlkBE,+BAEE,uCAAA,CADA,kBRqkBJ,CQ/jBA,SASE,2CAAA,CACA,mBAAA,CAFA,gCAAA,CADA,gBAAA,CADA,YAAA,CAMA,SAAA,CADA,uCAAA,CANA,mBAAA,CAJA,cAAA,CAYA,2BAAA,CATA,URykBF,CQ7jBE,eAEE,SAAA,CAIA,uBAAA,CAHA,oEACE,CAHF,URkkBJ,CQpjBA,MACE,WRujBF,CShtBA,MACE,6PTktBF,CS5sBA,cASE,mBAAA,CAFA,0CAAA,CACA,cAAA,CAFA,YAAA,CAIA,uCAAA,CACA,oBAAA,CAVA,iBAAA,CAEA,UAAA,CADA,QAAA,CAUA,qBAAA,CAPA,WAAA,CADA,STutBF,CS5sBE,aAfF,cAgBI,YT+sBF,CACF,CS5sBE,kCAEE,uCAAA,CADA,YT+sBJ,CS1sBE,qBACE,uCT4sBJ,CSxsBE,wCACE,+BT0sBJ,CSrsBE,oBAME,6BAAA,CADA,UAAA,CAJA,aAAA,CAEA,cAAA,CACA,aAAA,CAGA,2CAAA,CAAA,mCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CARA,aT+sBJ,CSnsBE,sBACE,cTqsBJ,CSlsBI,2BACE,2CTosBN,CS9rBI,kEAEE,uDAAA,CADA,+BTisBN,CUnwBE,8BACE,YVswBJ,CW3wBA,mBACE,GACE,SAAA,CACA,0BX8wBF,CW3wBA,GACE,SAAA,CACA,uBX6wBF,CACF,CWzwBA,mBACE,GACE,SX2wBF,CWxwBA,GACE,SX0wBF,CACF,CW/vBE,qBASE,2BAAA,CAFA,mCAAA,CAAA,2BAAA,CADA,0BAAA,CADA,WAAA,CAGA,SAAA,CAPA,cAAA,CACA,KAAA,CAEA,UAAA,CADA,SXuwBJ,CW7vBE,mBAcE,mDAAA,CANA,2CAAA,CACA,QAAA,CACA,mBAAA,CARA,QAAA,CASA,kDACE,CAPF,eAAA,CAEA,aAAA,CADA,SAAA,CALA,cAAA,CAGA,UAAA,CADA,SXwwBJ,CWzvBE,kBACE,aX2vBJ,CWvvBE,sBACE,YAAA,CACA,YXyvBJ,CWtvBI,oCACE,aXwvBN,CWnvBE,sBACE,mBXqvBJ,CWlvBI,6CACE,cXovBN,CK9oBI,0CMvGA,6CAKI,aAAA,CAEA,gBAAA,CACA,iBAAA,CAFA,UXsvBN,CACF,CW/uBE,kBACE,cXivBJ,CYl1BA,YACE,WAAA,CAIA,WZk1BF,CY/0BE,mBAEE,qBAAA,CADA,iBZk1BJ,CKrrBI,sCOtJE,4EACE,kBZ80BN,CY10BI,0JACE,mBZ40BN,CY70BI,8EACE,kBZ40BN,CACF,CYv0BI,0BAGE,UAAA,CAFA,aAAA,CACA,YZ00BN,CYr0BI,+BACE,eZu0BN,CYj0BE,8BACE,WZs0BJ,CYv0BE,8BACE,UZs0BJ,CYv0BE,8BAIE,iBZm0BJ,CYv0BE,8BAIE,kBZm0BJ,CYv0BE,oBAGE,cAAA,CADA,SZq0BJ,CYh0BI,aAPF,oBAQI,YZm0BJ,CACF,CYh0BI,gCACE,yCZk0BN,CY9zBI,wBACE,cAAA,CACA,kBZg0BN,CY7zBM,kCACE,oBZ+zBR,Cah4BA,qBAEE,Wb84BF,Cah5BA,qBAEE,Ub84BF,Cah5BA,WAQE,2CAAA,CACA,mBAAA,CANA,YAAA,CAOA,8BAAA,CALA,iBAAA,CAMA,SAAA,CALA,mBAAA,CACA,mBAAA,CANA,cAAA,CAcA,0BAAA,CAHA,wCACE,CATF,Sb44BF,Ca93BE,aAlBF,WAmBI,Ybi4BF,CACF,Ca93BE,mBAEE,SAAA,CADA,mBAAA,CAKA,uBAAA,CAHA,kEbi4BJ,Ca13BE,kBAEE,gCAAA,CADA,eb63BJ,Cc/5BA,aACE,gBAAA,CACA,iBdk6BF,Cc/5BE,sBAGE,WAAA,CADA,QAAA,CADA,Sdm6BJ,Cc75BE,oBAEE,eAAA,CADA,edg6BJ,Cc35BE,oBACE,iBd65BJ,Ccz5BE,mBAEE,YAAA,CACA,cAAA,CACA,6BAAA,CAHA,iBd85BJ,Ccx5BI,iDACE,yCd05BN,Cct5BI,6BACE,iBdw5BN,Ccn5BE,mBAGE,uCAAA,CACA,cAAA,CAHA,aAAA,CACA,cAAA,CAGA,sBdq5BJ,Ccl5BI,gDACE,+Bdo5BN,Cch5BI,4BACE,0CAAA,CACA,mBdk5BN,Cc74BE,mBAEE,SAAA,CADA,iBAAA,CAKA,2BAAA,CAHA,8Ddg5BJ,Cc14BI,qBAEE,aAAA,CADA,ed64BN,Ccx4BI,6BACE,SAAA,CACA,uBd04BN,Cex9BA,WAEE,0CAAA,CADA,+Bf49BF,Cex9BE,aALF,WAMI,Yf29BF,CACF,Cex9BE,kBACE,6BAAA,CAEA,aAAA,CADA,af29BJ,Cev9BI,gCACE,Yfy9BN,Cep9BE,iBAOE,eAAA,CANA,YAAA,CAKA,cAAA,CAGA,mBAAA,CAAA,eAAA,CADA,cAAA,CAGA,uCAAA,CADA,eAAA,CAEA,uBfk9BJ,Ce/8BI,8CACE,Ufi9BN,Ce78BI,+BACE,oBf+8BN,CKj0BI,0CUvIE,uBACE,af28BN,Cex8BM,yCACE,Yf08BR,CACF,Cer8BI,iCACE,gBfw8BN,Cez8BI,iCACE,iBfw8BN,Cez8BI,uBAEE,gBfu8BN,Cep8BM,iCACE,efs8BR,Ceh8BE,kBACE,WAAA,CAIA,eAAA,CADA,mBAAA,CAFA,6BAAA,CACA,cAAA,CAGA,kBfk8BJ,Ce97BE,mBAEE,YAAA,CADA,afi8BJ,Ce57BE,sBACE,gBAAA,CACA,Uf87BJ,Cez7BA,gBACE,gDf47BF,Cez7BE,uBACE,YAAA,CACA,cAAA,CACA,6BAAA,CACA,af27BJ,Cev7BE,kCACE,sCfy7BJ,Cet7BI,gFACE,+Bfw7BN,Ceh7BA,cAKE,wCAAA,CADA,gBAAA,CADA,iBAAA,CADA,eAAA,CADA,Ufu7BF,CK34BI,mCU7CJ,cASI,Ufm7BF,CACF,Ce/6BE,yBACE,sCfi7BJ,Ce16BA,WACE,mBAAA,CACA,SAAA,CAEA,cAAA,CADA,qBf86BF,CK15BI,mCUvBJ,WAQI,ef66BF,CACF,Ce16BE,iBACE,oBAAA,CAEA,aAAA,CACA,iBAAA,CAFA,Yf86BJ,Cez6BI,wBACE,ef26BN,Cev6BI,qBAGE,iBAAA,CAFA,gBAAA,CACA,mBf06BN,CgBhlCE,uBAME,kBAAA,CACA,mBAAA,CAHA,gCAAA,CACA,cAAA,CAJA,oBAAA,CAEA,eAAA,CADA,kBAAA,CAMA,gEhBmlCJ,CgB7kCI,gCAEE,2CAAA,CACA,uCAAA,CAFA,gChBilCN,CgB3kCI,0DAEE,0CAAA,CACA,sCAAA,CAFA,+BhB+kCN,CgBxkCE,gCAKE,4BhB6kCJ,CgBllCE,gEAME,6BhB4kCJ,CgBllCE,gCAME,4BhB4kCJ,CgBllCE,sBAIE,6DAAA,CAGA,8BAAA,CAJA,eAAA,CAFA,aAAA,CACA,eAAA,CAMA,sChB0kCJ,CgBrkCI,wDACE,6CAAA,CACA,8BhBukCN,CgBnkCI,+BACE,UhBqkCN,CiBxnCA,WAOE,2CAAA,CAGA,8CACE,CALF,gCAAA,CADA,aAAA,CAHA,MAAA,CADA,eAAA,CACA,OAAA,CACA,KAAA,CACA,SjB+nCF,CiBpnCE,aAfF,WAgBI,YjBunCF,CACF,CiBpnCE,mBAIE,2BAAA,CAHA,iEjBunCJ,CiBhnCE,mBACE,kDACE,CAEF,kEjBgnCJ,CiB1mCE,kBAEE,kBAAA,CADA,YAAA,CAEA,ejB4mCJ,CiBxmCE,mBAKE,kBAAA,CAEA,cAAA,CAHA,YAAA,CAIA,uCAAA,CALA,aAAA,CAFA,iBAAA,CAQA,uBAAA,CAHA,qBAAA,CAJA,SjBinCJ,CiBvmCI,yBACE,UjBymCN,CiBrmCI,iCACE,oBjBumCN,CiBnmCI,uCAEE,uCAAA,CADA,YjBsmCN,CiBjmCI,2BAEE,YAAA,CADA,ajBomCN,CKt/BI,0CY/GA,2BAMI,YjBmmCN,CACF,CiBhmCM,8DAIE,iBAAA,CAHA,aAAA,CAEA,aAAA,CADA,UjBomCR,CKphCI,mCYzEA,iCAII,YjB6lCN,CACF,CiB1lCM,wCACE,YjB4lCR,CiBxlCM,+CACE,oBjB0lCR,CK/hCI,sCYtDA,iCAII,YjBqlCN,CACF,CiBhlCE,kBAEE,YAAA,CACA,cAAA,CAFA,iBAAA,CAIA,8DACE,CAFF,kBjBmlCJ,CiB7kCI,oCAGE,SAAA,CADA,mBAAA,CAKA,6BAAA,CAHA,8DACE,CAJF,UjBmlCN,CiB1kCM,8CACE,8BjB4kCR,CiBvkCI,8BACE,ejBykCN,CiBpkCE,4BAGE,gBAAA,CAAA,kBjBwkCJ,CiB3kCE,4BAGE,iBAAA,CAAA,iBjBwkCJ,CiB3kCE,kBACE,WAAA,CAGA,eAAA,CAFA,aAAA,CAGA,kBjBskCJ,CiBnkCI,4CAGE,SAAA,CADA,mBAAA,CAKA,8BAAA,CAHA,8DACE,CAJF,UjBykCN,CiBhkCM,sDACE,6BjBkkCR,CiB9jCM,8DAGE,SAAA,CADA,mBAAA,CAKA,uBAAA,CAHA,8DACE,CAJF,SjBokCR,CiBzjCI,uCAGE,WAAA,CAFA,iBAAA,CACA,UjB4jCN,CiBtjCE,mBACE,YAAA,CACA,aAAA,CACA,cAAA,CAEA,+CACE,CAFF,kBjByjCJ,CiBnjCI,8DACE,WAAA,CACA,SAAA,CACA,oCjBqjCN,CiB5iCI,yBACE,QjB8iCN,CiBziCE,mBACE,YjB2iCJ,CKvmCI,mCY2DF,6BAQI,gBjB2iCJ,CiBnjCA,6BAQI,iBjB2iCJ,CiBnjCA,mBAKI,aAAA,CAEA,iBAAA,CADA,ajB6iCJ,CACF,CK/mCI,sCY2DF,6BAaI,kBjB2iCJ,CiBxjCA,6BAaI,mBjB2iCJ,CACF,CD1xCA,SAGE,uCAAA,CAFA,eAAA,CACA,eC8xCF,CD1xCE,eACE,mBAAA,CACA,cAAA,CAGA,eAAA,CADA,QAAA,CADA,SC8xCJ,CDxxCE,sCAEE,WAAA,CADA,iBAAA,CAAA,kBC2xCJ,CDtxCE,eACE,+BCwxCJ,CDrxCI,0CACE,+BCuxCN,CDjxCA,UAKE,wBmBaa,CnBZb,oBAAA,CAFA,UAAA,CAHA,oBAAA,CAEA,eAAA,CADA,0BAAA,CAAA,2BCwxCF,CmB1zCA,MACE,uMAAA,CACA,sLAAA,CACA,iNnB6zCF,CmBvzCA,QACE,eAAA,CACA,enB0zCF,CmBvzCE,eAKE,uCAAA,CAJA,aAAA,CAGA,eAAA,CADA,eAAA,CADA,eAAA,CAIA,sBnByzCJ,CmBtzCI,+BACE,YnBwzCN,CmBrzCM,mCAEE,WAAA,CADA,UnBwzCR,CmBhzCQ,sFAME,iBAAA,CALA,aAAA,CAGA,aAAA,CADA,cAAA,CAEA,kBAAA,CAHA,UnBszCV,CmB3yCE,cAGE,eAAA,CADA,QAAA,CADA,SnB+yCJ,CmBzyCE,cAGE,sBAAA,CAFA,YAAA,CACA,SAAA,CAEA,iBAAA,CACA,uBAAA,CACA,sBnB2yCJ,CmBxyCI,sBACE,uCnB0yCN,CmBnyCM,6EAEE,+BnBqyCR,CmBhyCI,2BAIE,iBnB+xCN,CmB3xCI,4CACE,gBnB6xCN,CmB9xCI,4CACE,iBnB6xCN,CmBzxCI,kBAME,iBAAA,CAFA,aAAA,CACA,YAAA,CAFA,iBnB4xCN,CmBrxCI,sGACE,+BAAA,CACA,cnBuxCN,CmBnxCI,4BACE,uCAAA,CACA,oBnBqxCN,CmBjxCI,0CACE,YnBmxCN,CmBhxCM,yDAIE,6BAAA,CAHA,aAAA,CAEA,WAAA,CAEA,qCAAA,CAAA,6BAAA,CAHA,UnBqxCR,CmB9wCM,kDACE,YnBgxCR,CmB1wCE,iCACE,YnB4wCJ,CmBzwCI,6CACE,WAAA,CAGA,WnBywCN,CmBpwCE,cACE,anBswCJ,CmBlwCE,gBACE,YnBowCJ,CKruCI,0CcxBA,0CASE,2CAAA,CAHA,YAAA,CACA,qBAAA,CACA,WAAA,CALA,MAAA,CADA,iBAAA,CACA,OAAA,CACA,KAAA,CACA,SnBmwCJ,CmBxvCI,+DACE,eAAA,CACA,enB0vCN,CmBtvCI,gCAQE,qDAAA,CAHA,uCAAA,CAEA,cAAA,CALA,aAAA,CAEA,kBAAA,CADA,wBAAA,CAFA,iBAAA,CAKA,kBnB0vCN,CmBrvCM,wDAEE,UnB4vCR,CmB9vCM,wDAEE,WnB4vCR,CmB9vCM,8CAIE,aAAA,CAEA,aAAA,CACA,YAAA,CANA,iBAAA,CAEA,SAAA,CAEA,YnByvCR,CmBpvCQ,oDAKE,6BAAA,CADA,UAAA,CAHA,aAAA,CAEA,WAAA,CAGA,2CAAA,CAAA,mCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAPA,UnB6vCV,CmBjvCM,8CAIE,2CAAA,CACA,gEACE,CALF,eAAA,CAEA,4BAAA,CADA,kBnBsvCR,CmB/uCQ,2DACE,YnBivCV,CmB5uCM,8CAGE,2CAAA,CADA,gCAAA,CADA,enBgvCR,CmB1uCM,yCAIE,aAAA,CAFA,UAAA,CAIA,YAAA,CADA,aAAA,CAJA,iBAAA,CACA,WAAA,CACA,SnB+uCR,CmBvuCI,+BACE,MnByuCN,CmBruCI,+BACE,4DnBuuCN,CmBpuCM,qDACE,+BnBsuCR,CmBnuCQ,sHACE,+BnBquCV,CmB/tCI,+BAEE,YAAA,CADA,mBnBkuCN,CmB9tCM,mCACE,enBguCR,CmB5tCM,6CACE,SnB8tCR,CmB1tCM,uDAGE,mBnB6tCR,CmBhuCM,uDAGE,kBnB6tCR,CmBhuCM,6CAIE,gBAAA,CAFA,aAAA,CADA,YnB+tCR,CmBztCQ,mDAKE,6BAAA,CADA,UAAA,CAHA,aAAA,CAEA,WAAA,CAGA,2CAAA,CAAA,mCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAPA,UnBkuCV,CmBltCM,+CACE,mBnBotCR,CmB5sCM,4CAEE,wBAAA,CADA,enB+sCR,CmB3sCQ,oEACE,mBnB6sCV,CmB9sCQ,oEACE,oBnB6sCV,CmBzsCQ,4EACE,iBnB2sCV,CmB5sCQ,4EACE,kBnB2sCV,CmBvsCQ,oFACE,mBnBysCV,CmB1sCQ,oFACE,oBnBysCV,CmBrsCQ,4FACE,mBnBusCV,CmBxsCQ,4FACE,oBnBusCV,CmBhsCE,mBACE,wBnBksCJ,CmB9rCE,wBACE,YAAA,CACA,SAAA,CAIA,0BAAA,CAHA,oEnBisCJ,CmB3rCI,kCACE,2BnB6rCN,CmBxrCE,gCACE,SAAA,CAIA,uBAAA,CAHA,qEnB2rCJ,CmBrrCI,8CAEE,kCAAA,CAAA,0BnBsrCN,CACF,CKx3CI,0Cc0MA,0CACE,YnBirCJ,CmB9qCI,yDACE,UnBgrCN,CmB5qCI,wDACE,YnB8qCN,CmB1qCI,kDACE,YnB4qCN,CmBvqCE,gBAIE,iDAAA,CADA,gCAAA,CAFA,aAAA,CACA,enB2qCJ,CACF,CKr7CM,+DcmRF,6CACE,YnBqqCJ,CmBlqCI,4DACE,UnBoqCN,CmBhqCI,2DACE,YnBkqCN,CmB9pCI,qDACE,YnBgqCN,CACF,CK76CI,mCc7JJ,QAgbI,oBnB8pCF,CmBxpCI,kCAME,qCAAA,CACA,qDAAA,CANA,eAAA,CACA,KAAA,CAGA,SnB0pCN,CmBrpCM,6CACE,uBnBupCR,CmBnpCM,gDACE,YnBqpCR,CmBhpCI,2CACE,kBnBmpCN,CmBppCI,2CACE,mBnBmpCN,CmBppCI,iCAEE,oBnBkpCN,CmB3oCI,yDACE,kBnB6oCN,CmB9oCI,yDACE,iBnB6oCN,CACF,CKt8CI,sCc7JJ,QA4dI,oBAAA,CACA,oDnB2oCF,CmBroCI,gCAME,qCAAA,CACA,qDAAA,CANA,eAAA,CACA,KAAA,CAGA,SnBuoCN,CmBloCM,8CACE,uBnBooCR,CmBhoCM,8CACE,YnBkoCR,CmB7nCI,yCACE,kBnBgoCN,CmBjoCI,yCACE,mBnBgoCN,CmBjoCI,+BAEE,oBnB+nCN,CmBxnCI,uDACE,kBnB0nCN,CmB3nCI,uDACE,iBnB0nCN,CmBrnCE,wBACE,YAAA,CACA,sBAAA,CAEA,SAAA,CACA,6FACE,CAHF,mBnBynCJ,CmBjnCI,sCACE,enBmnCN,CmB9mCE,iFACE,sBAAA,CAEA,SAAA,CACA,4FACE,CAHF,kBnBknCJ,CmBzmCE,iDACE,enB2mCJ,CmBvmCE,6CACE,YnBymCJ,CmBrmCE,uBACE,aAAA,CACA,enBumCJ,CmBpmCI,kCACE,enBsmCN,CmBlmCI,qCACE,enBomCN,CmBjmCM,0CACE,uCnBmmCR,CmB/lCM,6DACE,mBnBimCR,CmB7lCM,yFAEE,YnB+lCR,CmB1lCI,yCAEE,kBnB8lCN,CmBhmCI,yCAEE,mBnB8lCN,CmBhmCI,+BACE,aAAA,CAGA,SAAA,CADA,kBnB6lCN,CmBzlCM,2DACE,SnB2lCR,CmBrlCE,cAGE,kBAAA,CADA,YAAA,CAEA,gCAAA,CAHA,WnB0lCJ,CmBplCI,oBACE,uDnBslCN,CmBllCI,oBAME,6BAAA,CACA,kBAAA,CAFA,UAAA,CAJA,oBAAA,CAEA,WAAA,CAKA,2CAAA,CAAA,mCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CACA,yBAAA,CARA,qBAAA,CAFA,UnB8lCN,CmBjlCM,8BACE,wBnBmlCR,CmB/kCM,kKAEE,uBnBglCR,CmBlkCI,2EACE,YnBukCN,CmBpkCM,oDACE,anBskCR,CmBnkCQ,kEAKE,qCAAA,CACA,qDAAA,CAFA,YAAA,CAHA,eAAA,CACA,KAAA,CACA,SnBwkCV,CmBlkCU,0FACE,mBnBokCZ,CmB/jCQ,0EACE,QnBikCV,CmB5jCM,sFACE,kBnB8jCR,CmB/jCM,sFACE,mBnB8jCR,CmB1jCM,kDACE,uCnB4jCR,CmBtjCI,2CACE,sBAAA,CAEA,SAAA,CADA,kBnByjCN,CmBhjCI,qFAIE,mDnBmjCN,CmBvjCI,qFAIE,oDnBmjCN,CmBvjCI,2EACE,aAAA,CACA,oBAAA,CAGA,SAAA,CAFA,kBnBojCN,CmB/iCM,yFAEE,gBAAA,CADA,gBnBkjCR,CmB7iCM,0FACE,YnB+iCR,CACF,CoBtwDA,eAKE,eAAA,CACA,eAAA,CAJA,SpB6wDF,CoBtwDE,gCANA,kBAAA,CAFA,YAAA,CAGA,sBpBoxDF,CoB/wDE,iBAOE,mBAAA,CAFA,aAAA,CADA,gBAAA,CAEA,iBpBywDJ,CoBpwDE,wBAEE,qDAAA,CADA,uCpBuwDJ,CoBlwDE,qBACE,6CpBowDJ,CoB/vDI,sDAEE,uDAAA,CADA,+BpBkwDN,CoB9vDM,8DACE,+BpBgwDR,CoB3vDI,mCACE,uCAAA,CACA,oBpB6vDN,CoBzvDI,yBAKE,iBAAA,CADA,yCAAA,CAHA,aAAA,CAEA,eAAA,CADA,YpB8vDN,CqB9yDE,eAGE,+DAAA,CADA,oBAAA,CADA,qBrBmzDJ,CK9nDI,0CgBtLF,eAOI,YrBizDJ,CACF,CqB3yDM,6BACE,oBrB6yDR,CqBvyDE,kBACE,YAAA,CACA,qBAAA,CACA,SAAA,CACA,qBrByyDJ,CqBlyDI,0BACE,sBrBoyDN,CqBjyDM,gEACE,+BrBmyDR,CqB7xDE,gBAEE,uCAAA,CADA,erBgyDJ,CqB3xDE,kBACE,oBrB6xDJ,CqB1xDI,mCAGE,kBAAA,CAFA,YAAA,CACA,SAAA,CAEA,iBrB4xDN,CqBxxDI,oCAIE,kBAAA,CAHA,mBAAA,CACA,kBAAA,CACA,SAAA,CAGA,QAAA,CADA,iBrB2xDN,CqBtxDI,0DACE,kBrBwxDN,CqBzxDI,0DACE,iBrBwxDN,CqBpxDI,iDACE,uBAAA,CAEA,YrBqxDN,CqBhxDE,4BACE,YrBkxDJ,CqB3wDA,YAGE,kBAAA,CAFA,YAAA,CAIA,eAAA,CAHA,SAAA,CAIA,eAAA,CAFA,UrBgxDF,CqB3wDE,yBACE,WrB6wDJ,CqBtwDA,kBACE,YrBywDF,CKjsDI,0CgBzEJ,kBAKI,wBrBywDF,CACF,CqBtwDE,qCACE,WrBwwDJ,CK5tDI,sCgB7CF,+CAKI,kBrBwwDJ,CqB7wDA,+CAKI,mBrBwwDJ,CACF,CK9sDI,0CgBrDJ,6BAMI,SAAA,CAFA,eAAA,CACA,UrBqwDF,CqBlwDE,qDACE,gBrBowDJ,CqBjwDE,gDACE,SrBmwDJ,CqBhwDE,4CACE,iBAAA,CAAA,kBrBkwDJ,CqB/vDE,2CAEE,WAAA,CADA,crBkwDJ,CqB9vDE,2CACE,mBAAA,CACA,cAAA,CACA,SAAA,CACA,oBAAA,CAAA,iBrBgwDJ,CqB7vDE,2CACE,SrB+vDJ,CqB5vDE,qCAEE,WAAA,CACA,eAAA,CAFA,erBgwDJ,CACF,CsB16DA,MACE,qBAAA,CACA,yBtB66DF,CsBv6DA,aAME,qCAAA,CADA,cAAA,CAEA,0FACE,CAPF,cAAA,CACA,KAAA,CAaA,mDAAA,CACA,qBAAA,CAJA,wFACE,CATF,UAAA,CADA,StBi7DF,CuB57DA,MACE,mfvB+7DF,CuBz7DA,WACE,iBvB47DF,CK9xDI,mCkB/JJ,WAKI,evB47DF,CACF,CuBz7DE,kBACE,YvB27DJ,CuBv7DE,oBAEE,SAAA,CADA,SvB07DJ,CKvxDI,0CkBpKF,8BAOI,YvBk8DJ,CuBz8DA,8BAOI,avBk8DJ,CuBz8DA,oBAaI,2CAAA,CACA,kBAAA,CAJA,WAAA,CACA,eAAA,CACA,mBAAA,CANA,iBAAA,CAEA,SAAA,CAUA,uBAAA,CAHA,4CACE,CAPF,UvBg8DJ,CuBp7DI,+DACE,SAAA,CACA,oCvBs7DN,CACF,CK7zDI,mCkBjJF,8BAgCI,MvBy7DJ,CuBz9DA,8BAgCI,OvBy7DJ,CuBz9DA,oBAqCI,0BAAA,CADA,cAAA,CADA,QAAA,CAJA,cAAA,CAEA,KAAA,CAKA,sDACE,CALF,OvBu7DJ,CuB76DI,+DAME,YAAA,CACA,SAAA,CACA,4CACE,CARF,UvBk7DN,CACF,CK5zDI,0CkBxGA,+DAII,mBvBo6DN,CACF,CK12DM,+DkB/DF,+DASI,mBvBo6DN,CACF,CK/2DM,+DkB/DF,+DAcI,mBvBo6DN,CACF,CuB/5DE,kBAEE,kCAAA,CAAA,0BvBg6DJ,CK90DI,0CkBpFF,4BAOI,MvBw6DJ,CuB/6DA,4BAOI,OvBw6DJ,CuB/6DA,kBAWI,QAAA,CAEA,SAAA,CADA,eAAA,CANA,cAAA,CAEA,KAAA,CAWA,wBAAA,CALA,qGACE,CALF,OAAA,CADA,SvBs6DJ,CuBz5DI,4BACE,yBvB25DN,CuBv5DI,6DAEE,WAAA,CACA,SAAA,CAMA,uBAAA,CALA,sGACE,CAJF,UvB65DN,CACF,CKz3DI,mCkBjEF,4BA2CI,WvBu5DJ,CuBl8DA,4BA2CI,UvBu5DJ,CuBl8DA,kBA6CI,eAAA,CAHA,iBAAA,CAIA,8CAAA,CAFA,avBs5DJ,CACF,CKx5DM,+DkBOF,6DAII,avBi5DN,CACF,CKv4DI,sCkBfA,6DASI,avBi5DN,CACF,CuB54DE,iBAIE,2CAAA,CACA,0BAAA,CAFA,aAAA,CAFA,iBAAA,CAKA,2CACE,CALF,SvBk5DJ,CKp5DI,mCkBAF,iBAaI,0BAAA,CACA,mBAAA,CAFA,avB84DJ,CuBz4DI,uBACE,0BvB24DN,CACF,CuBv4DI,4DAEE,2CAAA,CACA,6BAAA,CACA,8BAAA,CAHA,gCvB44DN,CuBp4DE,4BAKE,mBAAA,CAAA,oBvBy4DJ,CuB94DE,4BAKE,mBAAA,CAAA,oBvBy4DJ,CuB94DE,kBAQE,gBAAA,CAFA,eAAA,CAFA,WAAA,CAHA,iBAAA,CAMA,sBAAA,CAJA,UAAA,CADA,SvB44DJ,CuBn4DI,+BACE,qBvBq4DN,CuBj4DI,kEAEE,uCvBk4DN,CuB93DI,6BACE,YvBg4DN,CKp6DI,0CkBaF,kBA8BI,eAAA,CADA,aAAA,CADA,UvBi4DJ,CACF,CK97DI,mCkBgCF,4BAmCI,mBvBi4DJ,CuBp6DA,4BAmCI,oBvBi4DJ,CuBp6DA,kBAqCI,aAAA,CADA,evBg4DJ,CuB53DI,+BACE,uCvB83DN,CuB13DI,mCACE,gCvB43DN,CuBx3DI,6DACE,kBvB03DN,CuBv3DM,8EACE,uCvBy3DR,CuBr3DM,0EACE,WvBu3DR,CACF,CuBj3DE,iBAIE,cAAA,CAHA,oBAAA,CAEA,aAAA,CAEA,kCACE,CAJF,YvBs3DJ,CuB92DI,uBACE,UvBg3DN,CuB52DI,yCAEE,UvBg3DN,CuBl3DI,yCAEE,WvBg3DN,CuBl3DI,+BACE,iBAAA,CAEA,SAAA,CACA,SvB82DN,CuB32DM,6CACE,oBvB62DR,CKp9DI,0CkB+FA,yCAaI,UvB62DN,CuB13DE,yCAaI,WvB62DN,CuB13DE,+BAcI,SvB42DN,CuBz2DM,+CACE,YvB22DR,CACF,CKh/DI,mCkBkHA,+BAwBI,mBvB02DN,CuBv2DM,8CACE,YvBy2DR,CACF,CuBn2DE,8BAEE,WvBw2DJ,CuB12DE,8BAEE,UvBw2DJ,CuB12DE,oBAKE,mBAAA,CAJA,iBAAA,CAEA,SAAA,CACA,SvBs2DJ,CK5+DI,0CkBkIF,8BASI,WvBs2DJ,CuB/2DA,8BASI,UvBs2DJ,CuB/2DA,oBAUI,SvBq2DJ,CACF,CuBl2DI,uCACE,iBvBw2DN,CuBz2DI,uCACE,kBvBw2DN,CuBz2DI,6BAEE,uCAAA,CACA,SAAA,CAIA,oBAAA,CAHA,+DvBq2DN,CuB/1DM,iDAEE,uCAAA,CADA,YvBk2DR,CuB71DM,gGAGE,SAAA,CADA,mBAAA,CAEA,kBvB81DR,CuB31DQ,sGACE,UvB61DV,CuBt1DE,8BAOE,mBAAA,CAAA,oBvB61DJ,CuBp2DE,8BAOE,mBAAA,CAAA,oBvB61DJ,CuBp2DE,oBAIE,kBAAA,CAKA,yCAAA,CANA,YAAA,CAKA,eAAA,CAFA,WAAA,CAKA,SAAA,CAVA,iBAAA,CACA,KAAA,CAUA,uBAAA,CAFA,kBAAA,CALA,UvB+1DJ,CKtiEI,mCkBkMF,8BAgBI,mBvBy1DJ,CuBz2DA,8BAgBI,oBvBy1DJ,CuBz2DA,oBAiBI,evBw1DJ,CACF,CuBr1DI,+DACE,SAAA,CACA,0BvBu1DN,CuBl1DE,6BAKE,+BvBq1DJ,CuB11DE,0DAME,gCvBo1DJ,CuB11DE,6BAME,+BvBo1DJ,CuB11DE,mBAIE,eAAA,CAHA,iBAAA,CAEA,UAAA,CADA,SvBw1DJ,CKriEI,0CkB2MF,mBAWI,QAAA,CADA,UvBq1DJ,CACF,CK9jEI,mCkB8NF,mBAiBI,SAAA,CADA,UAAA,CAEA,sBvBo1DJ,CuBj1DI,8DACE,8BAAA,CACA,SvBm1DN,CACF,CuB90DE,uBASE,kCAAA,CAAA,0BAAA,CAFA,2CAAA,CANA,WAAA,CACA,eAAA,CAIA,kBvB+0DJ,CuBz0DI,iEAZF,uBAaI,uBvB40DJ,CACF,CK3mEM,+DkBiRJ,uBAkBI,avB40DJ,CACF,CK1lEI,sCkB2PF,uBAuBI,avB40DJ,CACF,CK/lEI,mCkB2PF,uBA4BI,YAAA,CACA,yDAAA,CACA,oBvB40DJ,CuBz0DI,kEACE,evB20DN,CuBv0DI,6BACE,+CvBy0DN,CuBr0DI,0CAEE,YAAA,CADA,WvBw0DN,CuBn0DI,gDACE,oDvBq0DN,CuBl0DM,sDACE,0CvBo0DR,CACF,CuB7zDA,kBACE,gCAAA,CACA,qBvBg0DF,CuB7zDE,wBAME,qDAAA,CAFA,uCAAA,CAFA,gBAAA,CACA,kBAAA,CAFA,eAAA,CAIA,uBvBg0DJ,CKnoEI,mCkB8TF,kCAUI,mBvB+zDJ,CuBz0DA,kCAUI,oBvB+zDJ,CACF,CuB3zDE,wBAGE,eAAA,CADA,QAAA,CADA,SAAA,CAIA,wBAAA,CAAA,gBvB4zDJ,CuBxzDE,wBACE,yDvB0zDJ,CuBvzDI,oCACE,evByzDN,CuBpzDE,wBACE,aAAA,CAEA,YAAA,CADA,uBAAA,CAEA,gCvBszDJ,CuBnzDI,4DACE,uDvBqzDN,CuBjzDI,gDACE,mBvBmzDN,CuB9yDE,gCAKE,cAAA,CADA,aAAA,CAGA,YAAA,CANA,eAAA,CAKA,uBAAA,CAJA,KAAA,CACA,SvBozDJ,CuB7yDI,wCACE,YvB+yDN,CuB1yDI,wDACE,YvB4yDN,CuBxyDI,oCAGE,+BAAA,CADA,gBAAA,CADA,mBAAA,CAGA,2CvB0yDN,CKrrEI,mCkBuYA,8CAUI,mBvBwyDN,CuBlzDE,8CAUI,oBvBwyDN,CACF,CuBpyDI,oFAEE,uDAAA,CADA,+BvBuyDN,CuBjyDE,sCACE,2CvBmyDJ,CuB9xDE,2BAGE,eAAA,CADA,eAAA,CADA,iBvBkyDJ,CKtsEI,mCkBmaF,qCAOI,mBvBgyDJ,CuBvyDA,qCAOI,oBvBgyDJ,CACF,CuB5xDE,kCAEE,MvBkyDJ,CuBpyDE,kCAEE,OvBkyDJ,CuBpyDE,wBAME,uCAAA,CAFA,aAAA,CACA,YAAA,CAJA,iBAAA,CAEA,YvBiyDJ,CKhsEI,0CkB4ZF,wBAUI,YvB8xDJ,CACF,CuB3xDI,8BAKE,6BAAA,CADA,UAAA,CAHA,oBAAA,CAEA,WAAA,CAGA,+CAAA,CAAA,uCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAPA,UvBoyDN,CuB1xDM,wCACE,oBvB4xDR,CuBtxDE,8BAGE,uCAAA,CAFA,gBAAA,CACA,evByxDJ,CuBrxDI,iCAKE,gCAAA,CAHA,eAAA,CACA,eAAA,CACA,eAAA,CAHA,evB2xDN,CuBpxDM,sCACE,oBvBsxDR,CuBjxDI,iCAKE,gCAAA,CAHA,gBAAA,CACA,eAAA,CACA,eAAA,CAHA,avBuxDN,CuBhxDM,sCACE,oBvBkxDR,CuB5wDE,yBAKE,gCAAA,CAJA,aAAA,CAEA,gBAAA,CACA,iBAAA,CAFA,avBixDJ,CuB1wDE,uBAGE,wBAAA,CAFA,+BAAA,CACA,yBvB6wDJ,CwBj7EA,WACE,iBAAA,CACA,SxBo7EF,CwBj7EE,kBAOE,2CAAA,CACA,mBAAA,CACA,8BAAA,CAHA,gCAAA,CAHA,QAAA,CAEA,gBAAA,CADA,YAAA,CAMA,SAAA,CATA,iBAAA,CACA,sBAAA,CAaA,mCAAA,CAJA,oExBo7EJ,CwB76EI,6EACE,gBAAA,CACA,SAAA,CAKA,+BAAA,CAJA,8ExBg7EN,CwBx6EI,wBAWE,+BAAA,CAAA,8CAAA,CAFA,6BAAA,CAAA,8BAAA,CACA,YAAA,CAFA,UAAA,CAHA,QAAA,CAFA,QAAA,CAIA,kBAAA,CADA,iBAAA,CALA,iBAAA,CACA,KAAA,CAEA,OxBi7EN,CwBr6EE,iBAOE,mBAAA,CAFA,eAAA,CACA,oBAAA,CAHA,QAAA,CAFA,kBAAA,CAGA,aAAA,CAFA,SxB46EJ,CwBn6EE,iBACE,kBxBq6EJ,CwBj6EE,2BAGE,kBAAA,CAAA,oBxBu6EJ,CwB16EE,2BAGE,mBAAA,CAAA,mBxBu6EJ,CwB16EE,iBAIE,cAAA,CAHA,aAAA,CAKA,YAAA,CADA,uBAAA,CAEA,2CACE,CANF,UxBw6EJ,CwB95EI,8CACE,+BxBg6EN,CwB55EI,uBACE,qDxB85EN,CyBl/EA,YAIE,qBAAA,CADA,aAAA,CAGA,gBAAA,CALA,eAAA,CACA,UAAA,CAGA,azBs/EF,CyBl/EE,aATF,YAUI,YzBq/EF,CACF,CKv0EI,0CoB3KF,+BAKI,azB0/EJ,CyB//EA,+BAKI,czB0/EJ,CyB//EA,qBAWI,2CAAA,CAHA,aAAA,CAEA,WAAA,CANA,cAAA,CAEA,KAAA,CASA,uBAAA,CAHA,iEACE,CAJF,aAAA,CAFA,SzBw/EJ,CyB7+EI,mEACE,8BAAA,CACA,6BzB++EN,CyB5+EM,6EACE,8BzB8+ER,CyBz+EI,6CAEE,QAAA,CAAA,MAAA,CACA,QAAA,CACA,eAAA,CAHA,iBAAA,CACA,OAAA,CAGA,qBAAA,CAHA,KzB8+EN,CACF,CKt3EI,sCoBtKJ,YAuDI,QzBy+EF,CyBt+EE,mBACE,WzBw+EJ,CyBp+EE,6CACE,UzBs+EJ,CACF,CyBl+EE,uBACE,YAAA,CACA,OzBo+EJ,CKr4EI,mCoBjGF,uBAMI,QzBo+EJ,CyBj+EI,8BACE,WzBm+EN,CyB/9EI,qCACE,azBi+EN,CyB79EI,+CACE,kBzB+9EN,CACF,CyB19EE,wBAIE,uBAAA,CAOA,kCAAA,CAAA,0BAAA,CAVA,cAAA,CACA,eAAA,CACA,yDAAA,CAMA,oBzBy9EJ,CyBp9EI,2CAEE,YAAA,CADA,WzBu9EN,CyBl9EI,mEACE,+CzBo9EN,CyBj9EM,qHACE,oDzBm9ER,CyBh9EQ,iIACE,0CzBk9EV,CyBn8EE,wCAGE,wBACE,qBzBm8EJ,CyB/7EE,6BACE,kCzBi8EJ,CyBl8EE,6BACE,iCzBi8EJ,CACF,CK75EI,0CoB5BF,YAME,0BAAA,CADA,QAAA,CAEA,SAAA,CANA,cAAA,CACA,KAAA,CAMA,sDACE,CALF,OAAA,CADA,SzBk8EF,CyBv7EE,4CAEE,WAAA,CACA,SAAA,CACA,4CACE,CAJF,UzB47EJ,CACF,C0BzmFA,iBACE,GACE,Q1B2mFF,C0BxmFA,GACE,a1B0mFF,CACF,C0BtmFA,gBACE,GACE,SAAA,CACA,0B1BwmFF,C0BrmFA,IACE,S1BumFF,C0BpmFA,GACE,SAAA,CACA,uB1BsmFF,CACF,C0B9lFA,MACE,2eAAA,CACA,+fAAA,CACA,0lBAAA,CACA,kf1BgmFF,C0B1lFA,WAOE,kCAAA,CAAA,0BAAA,CANA,aAAA,CACA,gBAAA,CACA,eAAA,CAEA,uCAAA,CAGA,uBAAA,CAJA,kB1BgmFF,C0BzlFE,iBACE,U1B2lFJ,C0BvlFE,iBACE,oBAAA,CAEA,aAAA,CACA,qBAAA,CAFA,U1B2lFJ,C0BtlFI,+BACE,iB1BylFN,C0B1lFI,+BACE,kB1BylFN,C0B1lFI,qBAEE,gB1BwlFN,C0BplFI,kDACE,iB1BulFN,C0BxlFI,kDACE,kB1BulFN,C0BxlFI,kDAEE,iB1BslFN,C0BxlFI,kDAEE,kB1BslFN,C0BjlFE,iCAGE,iB1BslFJ,C0BzlFE,iCAGE,kB1BslFJ,C0BzlFE,uBACE,oBAAA,CACA,6BAAA,CAEA,eAAA,CACA,sBAAA,CACA,qB1BmlFJ,C0B/kFE,kBACE,YAAA,CAMA,gBAAA,CALA,SAAA,CAMA,oBAAA,CAHA,gBAAA,CAIA,WAAA,CAHA,eAAA,CAFA,SAAA,CADA,U1BulFJ,C0B9kFI,iDACE,4B1BglFN,C0B3kFE,iBACE,eAAA,CACA,sB1B6kFJ,C0B1kFI,gDACE,2B1B4kFN,C0BxkFI,kCAIE,kB1BglFN,C0BplFI,kCAIE,iB1BglFN,C0BplFI,wBAOE,6BAAA,CADA,UAAA,CALA,oBAAA,CAEA,YAAA,CAMA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CALA,uBAAA,CAHA,W1BklFN,C0BtkFI,iCACE,a1BwkFN,C0BpkFI,iCACE,gDAAA,CAAA,wC1BskFN,C0BlkFI,+BACE,8CAAA,CAAA,sC1BokFN,C0BhkFI,+BACE,8CAAA,CAAA,sC1BkkFN,C0B9jFI,sCACE,qDAAA,CAAA,6C1BgkFN,C0B1jFA,gBACE,Y1B6jFF,C0B1jFE,gCAIE,kB1B8jFJ,C0BlkFE,gCAIE,iB1B8jFJ,C0BlkFE,sBAGE,kBAAA,CAGA,uCAAA,CALA,mBAAA,CAIA,gBAAA,CAHA,S1BgkFJ,C0BzjFI,+BACE,aAAA,CACA,oB1B2jFN,C0BvjFI,2CACE,U1B0jFN,C0B3jFI,2CACE,W1B0jFN,C0B3jFI,iCAEE,kB1ByjFN,C0BrjFI,0BACE,W1BujFN,C2B9uFA,MACE,iSAAA,CACA,4UAAA,CACA,+NAAA,CACA,gZ3BivFF,C2BxuFE,iBAME,kDAAA,CADA,UAAA,CAJA,oBAAA,CAEA,cAAA,CAIA,mCAAA,CAAA,2BAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CANA,0BAAA,CAFA,a3BmvFJ,C2BvuFE,uBACE,6B3ByuFJ,C2BruFE,sBACE,wCAAA,CAAA,gC3BuuFJ,C2BnuFE,6BACE,+CAAA,CAAA,uC3BquFJ,C2BjuFE,4BACE,8CAAA,CAAA,sC3BmuFJ,C4B9wFA,SASE,2CAAA,CADA,gCAAA,CAJA,aAAA,CAGA,eAAA,CADA,aAAA,CADA,UAAA,CAFA,S5BqxFF,C4B5wFE,aAZF,SAaI,Y5B+wFF,CACF,CKpmFI,0CuBzLJ,SAkBI,Y5B+wFF,CACF,C4B5wFE,iBACE,mB5B8wFJ,C4B1wFE,yBAIE,iB5BixFJ,C4BrxFE,yBAIE,kB5BixFJ,C4BrxFE,eAQE,eAAA,CAPA,YAAA,CAMA,eAAA,CAJA,QAAA,CAEA,aAAA,CAHA,SAAA,CAWA,oBAAA,CAPA,kB5B+wFJ,C4BrwFI,kCACE,Y5BuwFN,C4BlwFE,eACE,aAAA,CACA,kBAAA,CAAA,mB5BowFJ,C4BjwFI,sCACE,aAAA,CACA,S5BmwFN,C4B7vFE,eAOE,kCAAA,CAAA,0BAAA,CANA,YAAA,CAEA,eAAA,CADA,gBAAA,CAMA,UAAA,CAJA,uCAAA,CACA,oBAAA,CAIA,8D5B8vFJ,C4BzvFI,0CACE,aAAA,CACA,S5B2vFN,C4BvvFI,6BAEE,kB5B0vFN,C4B5vFI,6BAEE,iB5B0vFN,C4B5vFI,mBAGE,iBAAA,CAFA,Y5B2vFN,C4BpvFM,2CACE,qB5BsvFR,C4BvvFM,2CACE,qB5ByvFR,C4B1vFM,2CACE,qB5B4vFR,C4B7vFM,2CACE,qB5B+vFR,C4BhwFM,2CACE,oB5BkwFR,C4BnwFM,2CACE,qB5BqwFR,C4BtwFM,2CACE,qB5BwwFR,C4BzwFM,2CACE,qB5B2wFR,C4B5wFM,4CACE,qB5B8wFR,C4B/wFM,4CACE,oB5BixFR,C4BlxFM,4CACE,qB5BoxFR,C4BrxFM,4CACE,qB5BuxFR,C4BxxFM,4CACE,qB5B0xFR,C4B3xFM,4CACE,qB5B6xFR,C4B9xFM,4CACE,oB5BgyFR,C4B1xFI,gCACE,SAAA,CAIA,yBAAA,CAHA,wC5B6xFN,C6Bh4FA,MACE,mS7Bm4FF,C6B13FE,mCACE,mBAAA,CACA,cAAA,CACA,QAAA,CAEA,mBAAA,CADA,kB7B83FJ,C6Bz3FE,oBAGE,kBAAA,CAOA,+CAAA,CACA,oBAAA,CAVA,mBAAA,CAIA,gBAAA,CACA,0BAAA,CACA,eAAA,CALA,QAAA,CAOA,qBAAA,CADA,eAAA,CAJA,wB7Bk4FJ,C6Bx3FI,0BAGE,uCAAA,CAFA,aAAA,CACA,YAAA,CAEA,6C7B03FN,C6Br3FM,gEAEE,0CAAA,CADA,+B7Bw3FR,C6Bl3FI,yBACE,uB7Bo3FN,C6B52FI,gCAME,oDAAA,CADA,UAAA,CAJA,oBAAA,CAEA,YAAA,CAIA,qCAAA,CAAA,6BAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CACA,iCAAA,CAPA,0BAAA,CAFA,W7Bu3FN,C6B12FI,wFACE,0C7B42FN,C8Bt7FA,iBACE,GACE,oB9By7FF,C8Bt7FA,IACE,kB9Bw7FF,C8Br7FA,GACE,oB9Bu7FF,CACF,C8B/6FA,MACE,yNAAA,CACA,sP9Bk7FF,C8B36FA,YA6BE,kCAAA,CAAA,0BAAA,CAVA,2CAAA,CACA,mBAAA,CACA,8BAAA,CAHA,gCAAA,CADA,sCAAA,CAdA,+IACE,CAYF,8BAAA,CAMA,SAAA,CArBA,iBAAA,CACA,uBAAA,CAyBA,4BAAA,CAJA,uDACE,CATF,6BAAA,CADA,S9B+6FF,C8B75FE,oBAEE,SAAA,CAKA,uBAAA,CAJA,2EACE,CAHF,S9Bk6FJ,C8Bx5FE,oBAEE,eAAA,CACA,wBAAA,CAAA,gBAAA,CAFA,U9B45FJ,C8Bv5FI,6CACE,qC9By5FN,C8Br5FI,uCAEE,eAAA,CADA,mB9Bw5FN,C8Bl5FI,6BACE,Y9Bo5FN,C8B/4FE,8CACE,sC9Bi5FJ,C8B74FE,mBAEE,gBAAA,CADA,a9Bg5FJ,C8B54FI,2CACE,Y9B84FN,C8B14FI,0CACE,e9B44FN,C8Bp4FA,eACE,iBAAA,CACA,eAAA,CAIA,YAAA,CAHA,kBAAA,CAEA,0BAAA,CADA,kB9By4FF,C8Bp4FE,yBACE,a9Bs4FJ,C8Bl4FE,oBACE,sCAAA,CACA,iB9Bo4FJ,C8Bh4FE,6BACE,oBAAA,CAGA,gB9Bg4FJ,C8B53FE,sBAYE,mBAAA,CANA,cAAA,CAHA,oBAAA,CACA,gBAAA,CAAA,iBAAA,CAIA,YAAA,CAGA,eAAA,CAVA,iBAAA,CAMA,wBAAA,CAAA,gBAAA,CAFA,uBAAA,CAHA,S9Bs4FJ,C8Bx3FI,qCACE,uB9B03FN,C8Bt3FI,cArBF,sBAsBI,W9By3FJ,C8Bt3FI,wCACE,2B9Bw3FN,C8Bp3FI,6BAOE,qCAAA,CACA,+CAAA,CAAA,uC9By3FN,C8B/2FI,yDAZE,UAAA,CADA,YAAA,CAKA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAVA,iBAAA,CACA,SAAA,CAEA,WAAA,CADA,U9B64FN,C8B93FI,4BAOE,oDAAA,CACA,4CAAA,CAAA,oCAAA,CAQA,uBAAA,CAJA,+C9Bk3FN,C8B32FM,gDACE,uB9B62FR,C8Bz2FM,mFACE,0C9B22FR,CACF,C8Bt2FI,0CAGE,2BAAA,CADA,uBAAA,CADA,S9B02FN,C8Bp2FI,8CACE,oB9Bs2FN,C8Bn2FM,aAJF,8CASI,8CAAA,CACA,iBAAA,CAHA,gCAAA,CADA,eAAA,CADA,cAAA,CAGA,kB9Bw2FN,C8Bn2FM,oDACE,mC9Bq2FR,CACF,C8Bz1FE,gCAEE,iBAAA,CADA,e9B61FJ,C8Bz1FI,mCACE,iB9B21FN,C8Bx1FM,oDAEE,a9Bu2FR,C8Bz2FM,oDAEE,c9Bu2FR,C8Bz2FM,0CAcE,8CAAA,CACA,iBAAA,CALA,gCAAA,CAEA,oBAAA,CACA,qBAAA,CANA,iBAAA,CACA,eAAA,CAHA,UAAA,CAIA,gBAAA,CALA,aAAA,CAEA,cAAA,CALA,iBAAA,CAUA,iBAAA,CARA,S9Bs2FR,C+BtnGA,MACE,wBAAA,CACA,wB/BynGF,C+BnnGA,aA+BE,kCAAA,CAAA,0BAAA,CAjBA,gCAAA,CADA,sCAAA,CAGA,SAAA,CADA,mBAAA,CAdA,iBAAA,CAGA,wDACE,CAgBF,4BAAA,CAGA,uEACE,CARF,uDACE,CANF,UAAA,CADA,S/BunGF,C+BhmGE,oBAuBE,8CAAA,CAAA,+CAAA,CADA,UAAA,CADA,aAAA,CAfA,gJACE,CANF,iBAAA,CAmBA,S/BolGJ,C+B7kGE,yBAGE,kEAAA,CAFA,gDAAA,CACA,6C/BglGJ,C+B3kGE,4BAGE,qEAAA,CADA,8CAAA,CADA,6C/B+kGJ,C+BzkGE,qBAEE,SAAA,CAKA,uBAAA,CAJA,wEACE,CAHF,S/B8kGJ,C+BpkGE,oBAqBE,uBAAA,CAEA,2CAAA,CACA,mBAAA,CACA,8BAAA,CAnBA,0FACE,CAaF,eAAA,CADA,8BAAA,CAlBA,iBAAA,CAqBA,oB/ByjGJ,C+BnjGI,uCAEE,YAAA,CADA,W/BsjGN,C+BjjGI,6CACE,oD/BmjGN,C+BhjGM,mDACE,0C/BkjGR,C+B1iGI,mCAwBE,eAAA,CACA,eAAA,CAxBA,oIACE,CAgBF,sCACE,CAIF,mBAAA,CAKA,wBAAA,CAAA,gBAAA,CAbA,sBAAA,CAAA,iB/BoiGN,C+BnhGI,4CACE,Y/BqhGN,C+BjhGI,2CACE,e/BmhGN,CgCtsGA,kBAME,ehCktGF,CgCxtGA,kBAME,gBhCktGF,CgCxtGA,QAUE,2CAAA,CACA,oBAAA,CAEA,8BAAA,CALA,uCAAA,CACA,cAAA,CALA,aAAA,CAGA,eAAA,CAKA,YAAA,CAPA,mBAAA,CAJA,cAAA,CACA,UAAA,CAiBA,yBAAA,CALA,mGACE,CAZF,ShCqtGF,CgClsGE,aAtBF,QAuBI,YhCqsGF,CACF,CgClsGE,kBACE,wBhCosGJ,CgChsGE,gBAEE,SAAA,CADA,mBAAA,CAGA,+BAAA,CADA,uBhCmsGJ,CgC/rGI,0BACE,8BhCisGN,CgC5rGE,4BAEE,0CAAA,CADA,+BhC+rGJ,CgC1rGE,YACE,oBAAA,CACA,oBhC4rGJ,CiCjvGA,oBACE,GACE,mBjCovGF,CACF,CiC5uGA,MACE,wfjC8uGF,CiCxuGA,YACE,aAAA,CAEA,eAAA,CADA,ajC4uGF,CiCxuGE,+BAOE,kBAAA,CAAA,kBjCyuGJ,CiChvGE,+BAOE,iBAAA,CAAA,mBjCyuGJ,CiChvGE,qBAQE,aAAA,CACA,cAAA,CACA,YAAA,CATA,iBAAA,CAKA,UjC0uGJ,CiCnuGI,qCAIE,iBjC2uGN,CiC/uGI,qCAIE,kBjC2uGN,CiC/uGI,2BAME,6BAAA,CADA,UAAA,CAJA,oBAAA,CAEA,YAAA,CAIA,yCAAA,CAAA,iCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CARA,WjC6uGN,CiChuGE,mBACE,iBAAA,CACA,UjCkuGJ,CiC9tGE,kBAWE,2CAAA,CACA,mBAAA,CACA,8BAAA,CALA,gCAAA,CACA,oBAAA,CAHA,kBAAA,CAFA,YAAA,CAUA,SAAA,CAPA,aAAA,CAFA,SAAA,CAJA,iBAAA,CASA,4BAAA,CARA,UAAA,CAaA,+CACE,CAbF,SjC4uGJ,CiC3tGI,+EACE,gBAAA,CACA,SAAA,CACA,sCjC6tGN,CiCvtGI,qCAEE,oCACE,gCjCwtGN,CiCptGI,2CACE,cjCstGN,CACF,CiCjtGE,kBACE,kBjCmtGJ,CiC/sGE,4BAGE,kBAAA,CAAA,oBjCstGJ,CiCztGE,4BAGE,mBAAA,CAAA,mBjCstGJ,CiCztGE,kBAKE,cAAA,CAJA,aAAA,CAMA,YAAA,CADA,uBAAA,CAEA,2CACE,CALF,kBAAA,CAFA,UjCutGJ,CiC5sGI,gDACE,+BjC8sGN,CiC1sGI,wBACE,qDjC4sGN,CkClzGA,MAEI,6VAAA,CAAA,uWAAA,CAAA,qPAAA,CAAA,2xBAAA,CAAA,qMAAA,CAAA,+aAAA,CAAA,2LAAA,CAAA,yPAAA,CAAA,2TAAA,CAAA,oaAAA,CAAA,2SAAA,CAAA,2LlC20GJ,CkC/zGE,4CAME,8CAAA,CACA,4BAAA,CACA,mBAAA,CACA,8BAAA,CAJA,mCAAA,CAJA,iBAAA,CAGA,gBAAA,CADA,iBAAA,CADA,eAAA,CASA,uBAAA,CADA,2BlCm0GJ,CkC/zGI,aAdF,4CAeI,elCk0GJ,CACF,CkC/zGI,sEACE,gClCi0GN,CkC5zGI,gDACE,qBlC8zGN,CkC1zGI,gIAEE,iBAAA,CADA,clC6zGN,CkCxzGI,4FACE,iBlC0zGN,CkCtzGI,kFACE,elCwzGN,CkCpzGI,0FACE,YlCszGN,CkClzGI,8EACE,mBlCozGN,CkC/yGE,sEAGE,iBAAA,CAAA,mBlCyzGJ,CkC5zGE,sEAGE,kBAAA,CAAA,kBlCyzGJ,CkC5zGE,sEASE,uBlCmzGJ,CkC5zGE,sEASE,wBlCmzGJ,CkC5zGE,sEAUE,4BlCkzGJ,CkC5zGE,4IAWE,6BlCizGJ,CkC5zGE,sEAWE,4BlCizGJ,CkC5zGE,kDAOE,0BAAA,CACA,WAAA,CAFA,eAAA,CADA,eAAA,CAHA,oBAAA,CAAA,iBAAA,CADA,iBlC2zGJ,CkC9yGI,kFACE,elCgzGN,CkC5yGI,oFAEE,UlCuzGN,CkCzzGI,oFAEE,WlCuzGN,CkCzzGI,gEAOE,wBhBiIU,CgBlIV,UAAA,CADA,WAAA,CAGA,kDAAA,CAAA,0CAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAVA,iBAAA,CAEA,UAAA,CACA,UlCqzGN,CkC1yGI,4DACE,4DlC4yGN,CkC9xGE,sDACE,oBlCiyGJ,CkC9xGI,gFACE,gClCgyGN,CkC3xGE,8DACE,0BlC8xGJ,CkC3xGI,4EACE,wBAlBG,CAmBH,kDAAA,CAAA,0ClC6xGN,CkCzxGI,0EACE,alC2xGN,CkChzGE,8DACE,oBlCmzGJ,CkChzGI,wFACE,gClCkzGN,CkC7yGE,sEACE,0BlCgzGJ,CkC7yGI,oFACE,wBAlBG,CAmBH,sDAAA,CAAA,8ClC+yGN,CkC3yGI,kFACE,alC6yGN,CkCl0GE,sDACE,oBlCq0GJ,CkCl0GI,gFACE,gClCo0GN,CkC/zGE,8DACE,0BlCk0GJ,CkC/zGI,4EACE,wBAlBG,CAmBH,kDAAA,CAAA,0ClCi0GN,CkC7zGI,0EACE,alC+zGN,CkCp1GE,oDACE,oBlCu1GJ,CkCp1GI,8EACE,gClCs1GN,CkCj1GE,4DACE,0BlCo1GJ,CkCj1GI,0EACE,wBAlBG,CAmBH,iDAAA,CAAA,yClCm1GN,CkC/0GI,wEACE,alCi1GN,CkCt2GE,4DACE,oBlCy2GJ,CkCt2GI,sFACE,gClCw2GN,CkCn2GE,oEACE,0BlCs2GJ,CkCn2GI,kFACE,wBAlBG,CAmBH,qDAAA,CAAA,6ClCq2GN,CkCj2GI,gFACE,alCm2GN,CkCx3GE,8DACE,oBlC23GJ,CkCx3GI,wFACE,gClC03GN,CkCr3GE,sEACE,0BlCw3GJ,CkCr3GI,oFACE,wBAlBG,CAmBH,sDAAA,CAAA,8ClCu3GN,CkCn3GI,kFACE,alCq3GN,CkC14GE,4DACE,oBlC64GJ,CkC14GI,sFACE,gClC44GN,CkCv4GE,oEACE,0BlC04GJ,CkCv4GI,kFACE,wBAlBG,CAmBH,qDAAA,CAAA,6ClCy4GN,CkCr4GI,gFACE,alCu4GN,CkC55GE,4DACE,oBlC+5GJ,CkC55GI,sFACE,gClC85GN,CkCz5GE,oEACE,0BlC45GJ,CkCz5GI,kFACE,wBAlBG,CAmBH,qDAAA,CAAA,6ClC25GN,CkCv5GI,gFACE,alCy5GN,CkC96GE,0DACE,oBlCi7GJ,CkC96GI,oFACE,gClCg7GN,CkC36GE,kEACE,0BlC86GJ,CkC36GI,gFACE,wBAlBG,CAmBH,oDAAA,CAAA,4ClC66GN,CkCz6GI,8EACE,alC26GN,CkCh8GE,oDACE,oBlCm8GJ,CkCh8GI,8EACE,gClCk8GN,CkC77GE,4DACE,0BlCg8GJ,CkC77GI,0EACE,wBAlBG,CAmBH,iDAAA,CAAA,yClC+7GN,CkC37GI,wEACE,alC67GN,CkCl9GE,4DACE,oBlCq9GJ,CkCl9GI,sFACE,gClCo9GN,CkC/8GE,oEACE,0BlCk9GJ,CkC/8GI,kFACE,wBAlBG,CAmBH,qDAAA,CAAA,6ClCi9GN,CkC78GI,gFACE,alC+8GN,CkCp+GE,wDACE,oBlCu+GJ,CkCp+GI,kFACE,gClCs+GN,CkCj+GE,gEACE,0BlCo+GJ,CkCj+GI,8EACE,wBAlBG,CAmBH,mDAAA,CAAA,2ClCm+GN,CkC/9GI,4EACE,alCi+GN,CmCroHA,MACE,qMnCwoHF,CmC/nHE,sBAEE,uCAAA,CADA,gBnCmoHJ,CmC/nHI,mCACE,anCioHN,CmCloHI,mCACE,cnCioHN,CmC7nHM,4BACE,sBnC+nHR,CmC5nHQ,mCACE,gCnC8nHV,CmC1nHQ,2DACE,SAAA,CAEA,uBAAA,CADA,enC6nHV,CmCxnHQ,yGACE,SAAA,CACA,uBnC0nHV,CmCtnHQ,yCACE,YnCwnHV,CmCjnHE,0BACE,eAAA,CACA,enCmnHJ,CmChnHI,+BACE,oBnCknHN,CmC7mHE,gDACE,YnC+mHJ,CmC3mHE,8BAIE,+BAAA,CAHA,oBAAA,CAEA,WAAA,CAGA,SAAA,CAKA,4BAAA,CAJA,4DACE,CAHF,0BnC+mHJ,CmCtmHI,aAdF,8BAeI,+BAAA,CACA,SAAA,CACA,uBnCymHJ,CACF,CmCtmHI,wCACE,6BnCwmHN,CmCpmHI,oCACE,+BnCsmHN,CmClmHI,qCAKE,6BAAA,CADA,UAAA,CAHA,oBAAA,CAEA,YAAA,CAGA,2CAAA,CAAA,mCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAPA,WnC2mHN,CmC9lHQ,mDACE,oBnCgmHV,CoC9sHE,kCAEE,iBpCotHJ,CoCttHE,kCAEE,kBpCotHJ,CoCttHE,wBAGE,yCAAA,CAFA,oBAAA,CAGA,SAAA,CACA,mCpCitHJ,CoC5sHI,aAVF,wBAWI,YpC+sHJ,CACF,CoC3sHE,6FAEE,SAAA,CACA,mCpC6sHJ,CoCvsHE,4FAEE,+BpCysHJ,CoCrsHE,oBACE,yBAAA,CACA,uBAAA,CAGA,yEpCqsHJ,CKtkHI,sC+BrHE,qDACE,uBpC8rHN,CACF,CoCzrHE,kEACE,yBpC2rHJ,CoCvrHE,sBACE,0BpCyrHJ,CqCpvHE,2BACE,arCuvHJ,CKlkHI,0CgCtLF,2BAKI,erCuvHJ,CqCpvHI,6BACE,iBrCsvHN,CACF,CqClvHI,6BAEE,0BAAA,CAAA,2BAAA,CADA,eAAA,CAEA,iBrCovHN,CqCjvHM,2CACE,kBrCmvHR,CqC7uHI,6CACE,QrC+uHN,CsC3wHE,uBACE,4CtC+wHJ,CsC1wHE,8CAJE,kCAAA,CAAA,0BtCkxHJ,CsC9wHE,uBACE,4CtC6wHJ,CsCxwHE,4BAEE,kCAAA,CAAA,0BAAA,CADA,qCtC2wHJ,CsCvwHI,mCACE,atCywHN,CsCrwHI,kCACE,atCuwHN,CsClwHE,0BAKE,eAAA,CAJA,aAAA,CAEA,YAAA,CACA,aAAA,CAFA,kBAAA,CAAA,mBtCuwHJ,CsCjwHI,uCACE,etCmwHN,CsC/vHI,sCACE,kBtCiwHN,CuC9yHA,MACE,oLvCizHF,CuCxyHE,oBAGE,iBAAA,CAEA,gBAAA,CADA,avC0yHJ,CuCtyHI,wCACE,uBvCwyHN,CuCpyHI,gCAEE,eAAA,CADA,gBvCuyHN,CuChyHM,wCACE,mBvCkyHR,CuC5xHE,8BAKE,oBvCgyHJ,CuCryHE,8BAKE,mBvCgyHJ,CuCryHE,8BAUE,4BvC2xHJ,CuCryHE,4DAWE,6BvC0xHJ,CuCryHE,8BAWE,4BvC0xHJ,CuCryHE,oBASE,cAAA,CANA,aAAA,CACA,eAAA,CAIA,evC6xHJ,CuCvxHI,kCACE,uCAAA,CACA,oBvCyxHN,CuCrxHI,wCAEE,uCAAA,CADA,YvCwxHN,CuCnxHI,oCAEE,WvCgyHN,CuClyHI,oCAEE,UvCgyHN,CuClyHI,0BAOE,6BAAA,CADA,UAAA,CADA,WAAA,CAGA,yCAAA,CAAA,iCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAVA,iBAAA,CAEA,UAAA,CAUA,sBAAA,CADA,yBAAA,CARA,UvC8xHN,CuClxHM,oCACE,wBvCoxHR,CuC/wHI,4BACE,YvCixHN,CuC5wHI,4CACE,YvC8wHN,CwCx2HE,+DACE,sBAAA,CAEA,mBAAA,CACA,0BAAA,CACA,uBxC02HJ,CwCv2HI,2EAGE,iBAAA,CADA,eAAA,CADA,yBxC22HN,CwCp2HE,mEACE,0BxCs2HJ,CwCl2HE,oBACE,qBxCo2HJ,CwCh2HE,gBACE,oBxCk2HJ,CwC91HE,gBACE,qBxCg2HJ,CwC51HE,iBACE,kBxC81HJ,CwC11HE,kBACE,kBxC41HJ,CyCr4HE,6BACE,sCzCw4HJ,CyCr4HE,cACE,yCzCu4HJ,CyC33HE,sIACE,oCzC63HJ,CyCr3HE,2EACE,qCzCu3HJ,CyC72HE,wGACE,oCzC+2HJ,CyCt2HE,yFACE,qCzCw2HJ,CyCn2HE,6BACE,kCzCq2HJ,CyC/1HE,6CACE,sCzCi2HJ,CyC11HE,4DACE,sCzC41HJ,CyCr1HE,4DACE,qCzCu1HJ,CyC90HE,yFACE,qCzCg1HJ,CyCx0HE,2EACE,sCzC00HJ,CyC/zHE,wHACE,qCzCi0HJ,CyC5zHE,8BAGE,mBAAA,CADA,gBAAA,CADA,gBzCg0HJ,CyC3zHE,eACE,4CzC6zHJ,CyC1zHE,eACE,4CzC4zHJ,CyCxzHE,gBAIE,+CAAA,CACA,kDAAA,CAJA,aAAA,CAEA,wBAAA,CADA,wBzC6zHJ,CyCtzHE,yBAOE,wCAAA,CACA,+DAAA,CACA,4BAAA,CACA,6BAAA,CARA,iBAAA,CAGA,eAAA,CACA,eAAA,CAFA,cAAA,CADA,oCAAA,CAFA,iBzCi0HJ,CyCrzHI,6BACE,YzCuzHN,CyCpzHM,kCACE,wBAAA,CACA,yBzCszHR,CyChzHE,iCAaE,wCAAA,CACA,+DAAA,CAJA,uCAAA,CACA,0BAAA,CALA,UAAA,CAJA,oBAAA,CAOA,2BAAA,CADA,2BAAA,CADA,2BAAA,CANA,eAAA,CAWA,wBAAA,CAAA,gBAAA,CAPA,SzCyzHJ,CyCvyHE,sBACE,iBAAA,CACA,iBzCyyHJ,CyCpyHE,iCAKE,ezCkyHJ,CyC/xHI,sCACE,gBzCiyHN,CyC7xHI,gDACE,YzC+xHN,CyCrxHA,gBACE,iBzCwxHF,CyCpxHE,yCACE,aAAA,CACA,SzCsxHJ,CyCjxHE,mBACE,YzCmxHJ,CyC9wHE,oBACE,QzCgxHJ,CyC5wHE,4BACE,WAAA,CACA,SAAA,CACA,ezC8wHJ,CyC3wHI,0CACE,YzC6wHN,CyCvwHE,yBAKE,wCAAA,CAEA,+BAAA,CADA,4BAAA,CAHA,eAAA,CADA,oDAAA,CAEA,wBAAA,CAAA,gBzC4wHJ,CyCrwHE,2BAEE,+DAAA,CADA,2BzCwwHJ,CyCpwHI,+BACE,uCAAA,CACA,gBzCswHN,CyCjwHE,sBACE,MAAA,CACA,WzCmwHJ,CyC9vHA,aACE,azCiwHF,CyCvvHE,4BAEE,aAAA,CADA,YzC2vHJ,CyCvvHI,wDAEE,2BAAA,CADA,wBzC0vHN,CyCpvHE,+BAKE,2CAAA,CAEA,+BAAA,CADA,gCAAA,CADA,sBAAA,CAHA,mBAAA,CACA,gBAAA,CAFA,azC4vHJ,CyCnvHI,qCAEE,UAAA,CACA,UAAA,CAFA,azCuvHN,CK93HI,0CoCsJF,8BACE,iBzC4uHF,CyCluHE,wSAGE,ezCwuHJ,CyCpuHE,sCAEE,mBAAA,CACA,eAAA,CADA,oBAAA,CADA,kBAAA,CAAA,mBzCwuHJ,CACF,C0CrkII,yDAIE,+BAAA,CACA,8BAAA,CAFA,aAAA,CADA,QAAA,CADA,iB1C2kIN,C0CnkII,uBAEE,uCAAA,CADA,c1CskIN,C0CjhIM,iHAEE,WAlDkB,CAiDlB,kB1C4hIR,C0C7hIM,6HAEE,WAlDkB,CAiDlB,kB1CwiIR,C0CziIM,6HAEE,WAlDkB,CAiDlB,kB1CojIR,C0CrjIM,oHAEE,WAlDkB,CAiDlB,kB1CgkIR,C0CjkIM,0HAEE,WAlDkB,CAiDlB,kB1C4kIR,C0C7kIM,uHAEE,WAlDkB,CAiDlB,kB1CwlIR,C0CzlIM,uHAEE,WAlDkB,CAiDlB,kB1ComIR,C0CrmIM,6HAEE,WAlDkB,CAiDlB,kB1CgnIR,C0CjnIM,yCAEE,WAlDkB,CAiDlB,kB1ConIR,C0CrnIM,yCAEE,WAlDkB,CAiDlB,kB1CwnIR,C0CznIM,0CAEE,WAlDkB,CAiDlB,kB1C4nIR,C0C7nIM,uCAEE,WAlDkB,CAiDlB,kB1CgoIR,C0CjoIM,wCAEE,WAlDkB,CAiDlB,kB1CooIR,C0CroIM,sCAEE,WAlDkB,CAiDlB,kB1CwoIR,C0CzoIM,wCAEE,WAlDkB,CAiDlB,kB1C4oIR,C0C7oIM,oCAEE,WAlDkB,CAiDlB,kB1CgpIR,C0CjpIM,2CAEE,WAlDkB,CAiDlB,kB1CopIR,C0CrpIM,qCAEE,WAlDkB,CAiDlB,kB1CwpIR,C0CzpIM,oCAEE,WAlDkB,CAiDlB,kB1C4pIR,C0C7pIM,kCAEE,WAlDkB,CAiDlB,kB1CgqIR,C0CjqIM,qCAEE,WAlDkB,CAiDlB,kB1CoqIR,C0CrqIM,mCAEE,WAlDkB,CAiDlB,kB1CwqIR,C0CzqIM,qCAEE,WAlDkB,CAiDlB,kB1C4qIR,C0C7qIM,wCAEE,WAlDkB,CAiDlB,kB1CgrIR,C0CjrIM,sCAEE,WAlDkB,CAiDlB,kB1CorIR,C0CrrIM,2CAEE,WAlDkB,CAiDlB,kB1CwrIR,C0C7qIM,iCAEE,WAPkB,CAMlB,iB1CgrIR,C0CjrIM,uCAEE,WAPkB,CAMlB,iB1CorIR,C0CrrIM,mCAEE,WAPkB,CAMlB,iB1CwrIR,C2C1wIA,MACE,2LAAA,CACA,yL3C6wIF,C2CpwIE,wBAKE,mBAAA,CAHA,YAAA,CACA,qBAAA,CACA,YAAA,CAHA,iB3C2wIJ,C2CjwII,8BAGE,QAAA,CACA,SAAA,CAHA,iBAAA,CACA,O3CqwIN,C2ChwIM,qCACE,0B3CkwIR,C2CruIM,kEACE,0C3CuuIR,C2CjuIE,2BAME,uBAAA,CADA,+DAAA,CAJA,YAAA,CACA,cAAA,CACA,aAAA,CACA,oB3CquIJ,C2ChuII,aATF,2BAUI,gB3CmuIJ,CACF,C2ChuII,cAGE,+BACE,iB3CguIN,C2C7tIM,sCAQE,qCAAA,CANA,QAAA,CAKA,UAAA,CAHA,aAAA,CAEA,UAAA,CAHA,MAAA,CAFA,iBAAA,CAaA,2CAAA,CALA,2DACE,CAGF,kDAAA,CARA,+B3CquIR,CACF,C2CvtII,8CACE,Y3CytIN,C2CrtII,iCAUE,+BAAA,CACA,6BAAA,CALA,uCAAA,CAEA,cAAA,CAPA,aAAA,CAGA,gBAAA,CACA,eAAA,CAFA,8BAAA,CAMA,+BAAA,CAGA,2CACE,CANF,kBAAA,CALA,U3CiuIN,C2CltIM,aAII,6CACE,O3CitIV,C2CltIQ,8CACE,O3CotIV,C2CrtIQ,8CACE,O3CutIV,C2CxtIQ,8CACE,O3C0tIV,C2C3tIQ,8CACE,O3C6tIV,C2C9tIQ,8CACE,O3CguIV,C2CjuIQ,8CACE,O3CmuIV,C2CpuIQ,8CACE,O3CsuIV,C2CvuIQ,8CACE,O3CyuIV,C2C1uIQ,+CACE,Q3C4uIV,C2C7uIQ,+CACE,Q3C+uIV,C2ChvIQ,+CACE,Q3CkvIV,C2CnvIQ,+CACE,Q3CqvIV,C2CtvIQ,+CACE,Q3CwvIV,C2CzvIQ,+CACE,Q3C2vIV,C2C5vIQ,+CACE,Q3C8vIV,C2C/vIQ,+CACE,Q3CiwIV,C2ClwIQ,+CACE,Q3CowIV,C2CrwIQ,+CACE,Q3CuwIV,C2CxwIQ,+CACE,Q3C0wIV,CACF,C2CrwIM,uCACE,gC3CuwIR,C2CnwIM,oDACE,a3CqwIR,C2ChwII,yCACE,S3CkwIN,C2C9vIM,2CACE,aAAA,CACA,8B3CgwIR,C2C1vIE,4BACE,U3C4vIJ,C2CzvII,aAJF,4BAKI,gB3C4vIJ,CACF,C2CxvIE,0BACE,Y3C0vIJ,C2CvvII,aAJF,0BAKI,a3C0vIJ,C2CtvIM,sCACE,O3CwvIR,C2CzvIM,uCACE,O3C2vIR,C2C5vIM,uCACE,O3C8vIR,C2C/vIM,uCACE,O3CiwIR,C2ClwIM,uCACE,O3CowIR,C2CrwIM,uCACE,O3CuwIR,C2CxwIM,uCACE,O3C0wIR,C2C3wIM,uCACE,O3C6wIR,C2C9wIM,uCACE,O3CgxIR,C2CjxIM,wCACE,Q3CmxIR,C2CpxIM,wCACE,Q3CsxIR,C2CvxIM,wCACE,Q3CyxIR,C2C1xIM,wCACE,Q3C4xIR,C2C7xIM,wCACE,Q3C+xIR,C2ChyIM,wCACE,Q3CkyIR,C2CnyIM,wCACE,Q3CqyIR,C2CtyIM,wCACE,Q3CwyIR,C2CzyIM,wCACE,Q3C2yIR,C2C5yIM,wCACE,Q3C8yIR,C2C/yIM,wCACE,Q3CizIR,CACF,C2C3yII,+FAEE,Q3C6yIN,C2C1yIM,yGACE,wBAAA,CACA,yB3C6yIR,C2CpyIM,2DAEE,wBAAA,CACA,yBAAA,CAFA,Q3CwyIR,C2CjyIM,iEACE,Q3CmyIR,C2ChyIQ,qLAGE,wBAAA,CACA,yBAAA,CAFA,Q3CoyIV,C2C9xIQ,6FACE,wBAAA,CACA,yB3CgyIV,C2C3xIM,yDACE,kB3C6xIR,C2CxxII,sCACE,Q3C0xIN,C2CrxIE,2BAEE,iBAAA,CAOA,kBAAA,CAHA,uCAAA,CAEA,cAAA,CAPA,aAAA,CAGA,YAAA,CACA,gBAAA,CAEA,mBAAA,CAGA,gCAAA,CAPA,W3C8xIJ,C2CpxII,iCAEE,uDAAA,CADA,+B3CuxIN,C2ClxII,iCAKE,6BAAA,CADA,UAAA,CAHA,aAAA,CAEA,WAAA,CAGA,8CAAA,CAAA,sCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CACA,+CACE,CATF,U3C4xIN,C2C7wIE,4BAOE,yEACE,CANF,YAAA,CAGA,aAAA,CAFA,qBAAA,CAGA,mBAAA,CALA,iBAAA,CAYA,wBAAA,CATA,Y3CmxIJ,C2CvwII,sCACE,wB3CywIN,C2CrwII,oCACE,S3CuwIN,C2CnwII,kCAGE,wEACE,CAFF,mBAAA,CADA,O3CuwIN,C2C7vIM,uDACE,8CAAA,CAAA,sC3C+vIR,CKt4II,0CsCqJF,wDAEE,kB3CuvIF,C2CzvIA,wDAEE,mB3CuvIF,C2CzvIA,8CAGE,eAAA,CAFA,eAAA,CAGA,iC3CqvIF,C2CjvIE,8DACE,mB3CovIJ,C2CrvIE,8DACE,kB3CovIJ,C2CrvIE,oDAEE,U3CmvIJ,C2C/uIE,8EAEE,kB3CkvIJ,C2CpvIE,8EAEE,mB3CkvIJ,C2CpvIE,8EAGE,kB3CivIJ,C2CpvIE,8EAGE,mB3CivIJ,C2CpvIE,oEACE,U3CmvIJ,C2C7uIE,8EAEE,mB3CgvIJ,C2ClvIE,8EAEE,kB3CgvIJ,C2ClvIE,8EAGE,mB3C+uIJ,C2ClvIE,8EAGE,kB3C+uIJ,C2ClvIE,oEACE,U3CivIJ,CACF,C2CnuIE,cAHF,olDAII,gC3CsuIF,C2CnuIE,g8GACE,uC3CquIJ,CACF,C2ChuIA,4sDACE,+B3CmuIF,C2C/tIA,wmDACE,a3CkuIF,C4CtmJA,MACE,qWAAA,CACA,8W5CymJF,C4ChmJE,4BAEE,oBAAA,CADA,iB5ComJJ,C4C/lJI,sDAEE,S5CkmJN,C4CpmJI,sDAEE,U5CkmJN,C4CpmJI,4CACE,iBAAA,CAEA,S5CimJN,C4C5lJE,+CAEE,SAAA,CADA,U5C+lJJ,C4C1lJE,kDAEE,W5CqmJJ,C4CvmJE,kDAEE,Y5CqmJJ,C4CvmJE,wCAOE,qDAAA,CADA,UAAA,CADA,aAAA,CAGA,0CAAA,CAAA,kCAAA,CAEA,4BAAA,CAAA,oBAAA,CADA,6BAAA,CAAA,qBAAA,CAEA,yBAAA,CAAA,iBAAA,CAVA,iBAAA,CAEA,SAAA,CACA,Y5CmmJJ,C4CxlJE,gEACE,wB1B2Wa,C0B1Wb,mDAAA,CAAA,2C5C0lJJ,C6C1oJA,QACE,8DAAA,CAGA,+CAAA,CACA,iEAAA,CACA,oDAAA,CACA,sDAAA,CACA,mDAAA,CAGA,qEAAA,CACA,qEAAA,CACA,wEAAA,CACA,0EAAA,CACA,wEAAA,CACA,yEAAA,CACA,kEAAA,CACA,+DAAA,CACA,oEAAA,CACA,oEAAA,CACA,mEAAA,CACA,gEAAA,CACA,uEAAA,CACA,mEAAA,CACA,qEAAA,CACA,oEAAA,CACA,gEAAA,CACA,wEAAA,CACA,qEAAA,CACA,+D7CyoJF,C6CnoJA,SAEE,kBAAA,CADA,Y7CuoJF,C8CzqJE,kBAUE,cAAA,CATA,YAAA,CACA,kEACE,CAQF,Y9CqqJJ,C8CjqJI,sDACE,gB9CmqJN,C8C7pJI,oFAKE,wDAAA,CACA,mBAAA,CAJA,aAAA,CAEA,QAAA,CADA,aAAA,CAIA,sC9C+pJN,C8C1pJM,iOACE,kBAAA,CACA,8B9C6pJR,C8CzpJM,6FACE,iBAAA,CAAA,c9C4pJR,C8CxpJM,2HACE,Y9C2pJR,C8CvpJM,wHACE,e9C0pJR,C8C3oJI,yMAGE,eAAA,CAAA,Y9CmpJN,C8CroJI,ybAOE,W9C2oJN,C8CvoJI,8BACE,eAAA,CAAA,Y9CyoJN,CKrkJI,mC0ChKA,8BACE,U/C6uJJ,C+C9uJE,8BACE,W/C6uJJ,C+C9uJE,8BAGE,kB/C2uJJ,C+C9uJE,8BAGE,iB/C2uJJ,C+C9uJE,oBAKE,mBAAA,CADA,YAAA,CAFA,a/C4uJJ,C+CtuJI,kCACE,W/CyuJN,C+C1uJI,kCACE,U/CyuJN,C+C1uJI,kCAEE,iBAAA,CAAA,c/CwuJN,C+C1uJI,kCAEE,aAAA,CAAA,kB/CwuJN,CACF","file":"main.css"}
\ No newline at end of file
diff --git a/assets/stylesheets/main.35f28582.min.css b/assets/stylesheets/main.35f28582.min.css
deleted file mode 100644
index e184017..0000000
--- a/assets/stylesheets/main.35f28582.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@charset "UTF-8";html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;box-sizing:border-box}*,:after,:before{box-sizing:inherit}@media (prefers-reduced-motion){*,:after,:before{transition:none!important}}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{border:0;box-sizing:initial;display:block;height:.05rem;overflow:visible;padding:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{background:#0000;border:0;font-family:inherit;font-size:inherit;margin:0;padding:0}input{border:0;outline:none}:root{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3;--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:#526cfe1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-scheme=default]{color-scheme:light}[data-md-color-scheme=default] img[src$="#gh-dark-mode-only"],[data-md-color-scheme=default] img[src$="#only-dark"]{display:none}:root,[data-md-color-scheme=default]{--md-hue:225deg;--md-default-fg-color:#000000de;--md-default-fg-color--light:#0000008a;--md-default-fg-color--lighter:#00000052;--md-default-fg-color--lightest:#00000012;--md-default-bg-color:#fff;--md-default-bg-color--light:#ffffffb3;--md-default-bg-color--lighter:#ffffff4d;--md-default-bg-color--lightest:#ffffff1f;--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:#4287ff;--md-code-hl-color--light:#4287ff1a;--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-del-color:#f5503d26;--md-typeset-ins-color:#0bd57026;--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-mark-color:#ffff0080;--md-typeset-table-color:#0000001f;--md-typeset-table-color--light:rgba(0,0,0,.035);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-warning-fg-color:#000000de;--md-warning-bg-color:#ff9;--md-footer-fg-color:#fff;--md-footer-fg-color--light:#ffffffb3;--md-footer-fg-color--lighter:#ffffff73;--md-footer-bg-color:#000000de;--md-footer-bg-color--dark:#00000052;--md-shadow-z1:0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a;--md-shadow-z2:0 0.2rem 0.5rem #0000001a,0 0 0.05rem #00000040;--md-shadow-z3:0 0.2rem 0.5rem #0003,0 0 0.05rem #00000059}.md-icon svg{fill:currentcolor;display:block;height:1.2rem;width:1.2rem}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--md-text-font-family:var(--md-text-font,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;--md-code-font-family:var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,monospace}aside,body,input{font-feature-settings:"kern","liga";color:var(--md-typeset-color);font-family:var(--md-text-font-family)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family)}:root{--md-typeset-table-sort-icon:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--asc:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--desc:url('data:image/svg+xml;charset=utf-8,')}.md-typeset{-webkit-print-color-adjust:exact;color-adjust:exact;font-size:.8rem;line-height:1.6}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{margin-bottom:1em;margin-top:1em}.md-typeset h1{color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3;margin:0 0 1.25em}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{font-size:1.5625em;line-height:1.4;margin:1.6em 0 .64em}.md-typeset h3{font-size:1.25em;font-weight:400;letter-spacing:-.01em;line-height:1.5;margin:1.6em 0 .8em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{font-weight:700;letter-spacing:-.01em;margin:1em 0}.md-typeset h5,.md-typeset h6{color:var(--md-default-fg-color--light);font-size:.8em;font-weight:700;letter-spacing:-.01em;margin:1.25em 0}.md-typeset h5{text-transform:uppercase}.md-typeset hr{border-bottom:.05rem solid var(--md-default-fg-color--lightest);display:flow-root;margin:1.5em 0}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset a:focus code,.md-typeset a:hover code{background-color:var(--md-accent-fg-color--transparent)}.md-typeset a code{color:currentcolor;transition:background-color 125ms}.md-typeset a.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr;font-variant-ligatures:none}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone;font-size:.85em;padding:0 .2941176471em;word-break:break-word}.md-typeset code:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-typeset pre{display:flow-root;line-height:1.4;position:relative}.md-typeset pre>code{-webkit-box-decoration-break:slice;box-decoration-break:slice;box-shadow:none;display:block;margin:0;outline-color:var(--md-accent-fg-color);overflow:auto;padding:.7720588235em 1.1764705882em;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin;touch-action:auto;word-break:normal}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-typeset pre>code::-webkit-scrollbar{height:.2rem;width:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-typeset kbd{background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset;color:var(--md-default-fg-color);display:inline-block;font-size:.75em;padding:0 .6666666667em;vertical-align:text-top;word-break:break-word}.md-typeset mark{background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:inherit;word-break:break-word}.md-typeset abbr{border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help;text-decoration:none}.md-typeset small{opacity:.75}[dir=ltr] .md-typeset sub,[dir=ltr] .md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em}[dir=ltr] .md-typeset blockquote{padding-left:.6rem}[dir=rtl] .md-typeset blockquote{padding-right:.6rem}[dir=ltr] .md-typeset blockquote{border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{border-right:.2rem solid var(--md-default-fg-color--lighter)}.md-typeset blockquote{color:var(--md-default-fg-color--light);margin-left:0;margin-right:0}.md-typeset ul{list-style-type:disc}[dir=ltr] .md-typeset ol,[dir=ltr] .md-typeset ul{margin-left:.625em}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em}.md-typeset ol,.md-typeset ul{padding:0}.md-typeset ol:not([hidden]),.md-typeset ul:not([hidden]){display:flow-root}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}[dir=ltr] .md-typeset ol li,[dir=ltr] .md-typeset ul li{margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}[dir=ltr] .md-typeset ol li ol,[dir=ltr] .md-typeset ol li ul,[dir=ltr] .md-typeset ul li ol,[dir=ltr] .md-typeset ul li ul{margin-left:.625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset dd{margin-left:1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em}.md-typeset dd{margin-bottom:1.5em;margin-top:1em}.md-typeset img,.md-typeset svg,.md-typeset video{height:auto;max-width:100%}.md-typeset img[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child{margin-top:0}.md-typeset figure{display:flow-root;margin:1em auto;max-width:100%;text-align:center;width:-moz-fit-content;width:fit-content}.md-typeset figure img{display:block;margin:0 auto}.md-typeset figcaption{font-style:italic;margin:1em auto;max-width:24rem}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){background-color:var(--md-default-bg-color);border:.05rem solid var(--md-typeset-table-color);border-radius:.1rem;display:inline-block;font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) td{border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) tbody tr{transition:background-color 125ms}.md-typeset table:not([class]) tbody tr:hover{background-color:var(--md-typeset-table-color--light);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}[dir=ltr] .md-typeset table th[role=columnheader]:after{margin-left:.5em}[dir=rtl] .md-typeset table th[role=columnheader]:after{margin-right:.5em}.md-typeset table th[role=columnheader]:after{content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-typeset-table-sort-icon);mask-image:var(--md-typeset-table-sort-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset table th[role=columnheader]:hover:after{background-color:var(--md-default-fg-color--lighter)}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--asc);mask-image:var(--md-typeset-table-sort-icon--asc)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--desc);mask-image:var(--md-typeset-table-sort-icon--desc)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;margin:0;overflow:hidden;width:100%}@media screen and (max-width:44.984375em){.md-content__inner>pre{margin:1em -.8rem}.md-content__inner>pre code{border-radius:0}}.md-typeset .md-author{border-radius:100%;display:block;flex-shrink:0;height:1.6rem;overflow:hidden;position:relative;transition:color 125ms,transform 125ms;width:1.6rem}.md-typeset .md-author img{display:block}.md-typeset .md-author--more{background:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--lighter);font-size:.6rem;font-weight:700;line-height:1.6rem;text-align:center}.md-typeset .md-author--long{height:2.4rem;width:2.4rem}.md-typeset a.md-author{transform:scale(1)}.md-typeset a.md-author img{border-radius:100%;filter:grayscale(100%) opacity(75%);transition:filter 125ms}.md-typeset a.md-author:focus,.md-typeset a.md-author:hover{transform:scale(1.1);z-index:1}.md-typeset a.md-author:focus img,.md-typeset a.md-author:hover img{filter:grayscale(0)}.md-banner{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color);overflow:auto}@media print{.md-banner{display:none}}.md-banner--warning{background-color:var(--md-warning-bg-color);color:var(--md-warning-fg-color)}.md-banner__inner{font-size:.7rem;margin:.6rem auto;padding:0 .8rem}[dir=ltr] .md-banner__button{float:right}[dir=rtl] .md-banner__button{float:left}.md-banner__button{color:inherit;cursor:pointer;transition:opacity .25s}.no-js .md-banner__button{display:none}.md-banner__button:hover{opacity:.7}html{font-size:125%;height:100%;overflow-x:hidden}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;font-size:.5rem;min-height:100%;position:relative;width:100%}@media print{body{display:block}}@media screen and (max-width:59.984375em){body[data-md-scrolllock]{position:fixed}}.md-grid{margin-left:auto;margin-right:auto;max-width:61rem}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;text-overflow:ellipsis}.md-toggle{display:none}.md-option{height:0;opacity:0;position:absolute;width:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-color:var(--md-accent-fg-color);outline-style:auto}.md-skip{background-color:var(--md-default-fg-color);border-radius:.1rem;color:var(--md-default-bg-color);font-size:.64rem;margin:.5rem;opacity:0;outline-color:var(--md-accent-fg-color);padding:.3rem .5rem;position:fixed;transform:translateY(.4rem);z-index:-1}.md-skip:focus{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms;z-index:10}@page{margin:25mm}:root{--md-clipboard-icon:url('data:image/svg+xml;charset=utf-8,')}.md-clipboard{border-radius:.1rem;color:var(--md-default-fg-color--lightest);cursor:pointer;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;position:absolute;right:.5em;top:.5em;transition:color .25s;width:1.5em;z-index:1}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .md-code__content{display:grid}@keyframes consent{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes overlay{0%{opacity:0}to{opacity:1}}.md-consent__overlay{animation:overlay .25s both;-webkit-backdrop-filter:blur(.1rem);backdrop-filter:blur(.1rem);background-color:#0000008a;height:100%;opacity:1;position:fixed;top:0;width:100%;z-index:5}.md-consent__inner{animation:consent .5s cubic-bezier(.1,.7,.1,1) both;background-color:var(--md-default-bg-color);border:0;border-radius:.1rem;bottom:0;box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;max-height:100%;overflow:auto;padding:0;position:fixed;width:100%;z-index:5}.md-consent__form{padding:.8rem}.md-consent__settings{display:none;margin:1em 0}input:checked+.md-consent__settings{display:block}.md-consent__controls{margin-bottom:.8rem}.md-typeset .md-consent__controls .md-button{display:inline}@media screen and (max-width:44.984375em){.md-typeset .md-consent__controls .md-button{display:block;margin-top:.4rem;text-align:center;width:100%}}.md-consent label{cursor:pointer}.md-content{flex-grow:1;min-width:0}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){[dir=ltr] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=ltr] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner,[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}}.md-content__inner:before{content:"";display:block;height:.4rem}.md-content__inner>:last-child{margin-bottom:0}[dir=ltr] .md-content__button{float:right}[dir=rtl] .md-content__button{float:left}[dir=ltr] .md-content__button{margin-left:.4rem}[dir=rtl] .md-content__button{margin-right:.4rem}.md-content__button{margin:.4rem 0;padding:0}@media print{.md-content__button{display:none}}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}[dir=ltr] .md-dialog{right:.8rem}[dir=rtl] .md-dialog{left:.8rem}.md-dialog{background-color:var(--md-default-fg-color);border-radius:.1rem;bottom:.8rem;box-shadow:var(--md-shadow-z3);min-width:11.1rem;opacity:0;padding:.4rem .6rem;pointer-events:none;position:fixed;transform:translateY(100%);transition:transform 0ms .4s,opacity .4s;z-index:4}@media print{.md-dialog{display:none}}.md-dialog--active{opacity:1;pointer-events:auto;transform:translateY(0);transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-feedback{margin:2em 0 1em;text-align:center}.md-feedback fieldset{border:none;margin:0;padding:0}.md-feedback__title{font-weight:700;margin:1em auto}.md-feedback__inner{position:relative}.md-feedback__list{display:flex;flex-wrap:wrap;place-content:baseline center;position:relative}.md-feedback__list:hover .md-icon:not(:disabled){color:var(--md-default-fg-color--lighter)}:disabled .md-feedback__list{min-height:1.8rem}.md-feedback__icon{color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;margin:0 .1rem;transition:color 125ms}.md-feedback__icon:not(:disabled).md-icon:hover{color:var(--md-accent-fg-color)}.md-feedback__icon:disabled{color:var(--md-default-fg-color--lightest);pointer-events:none}.md-feedback__note{opacity:0;position:relative;transform:translateY(.4rem);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-feedback__note>*{margin:0 auto;max-width:16rem}:disabled .md-feedback__note{opacity:1;transform:translateY(0)}.md-footer{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color)}@media print{.md-footer{display:none}}.md-footer__inner{justify-content:space-between;overflow:auto;padding:.2rem}.md-footer__inner:not([hidden]){display:flex}.md-footer__link{align-items:end;display:flex;flex-grow:0.01;margin-bottom:.4rem;margin-top:1rem;max-width:100%;outline-color:var(--md-accent-fg-color);overflow:hidden;transition:opacity .25s}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}[dir=rtl] .md-footer__link svg{transform:scaleX(-1)}@media screen and (max-width:44.984375em){.md-footer__link--prev{flex-shrink:0}.md-footer__link--prev .md-footer__title{display:none}}[dir=ltr] .md-footer__link--next{margin-left:auto}[dir=rtl] .md-footer__link--next{margin-right:auto}.md-footer__link--next{text-align:right}[dir=rtl] .md-footer__link--next{text-align:left}.md-footer__title{flex-grow:1;font-size:.9rem;margin-bottom:.7rem;max-width:calc(100% - 2.4rem);padding:0 1rem;white-space:nowrap}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-copyright{color:var(--md-footer-fg-color--lighter);font-size:.64rem;margin:auto .6rem;padding:.4rem 0;width:100%}@media screen and (min-width:45em){.md-copyright{width:auto}}.md-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-social{display:inline-flex;gap:.2rem;margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-social{padding:.6rem 0}}.md-social__link{display:inline-block;height:1.6rem;text-align:center;width:1.6rem}.md-social__link:before{line-height:1.9}.md-social__link svg{fill:currentcolor;max-height:.8rem;vertical-align:-25%}.md-typeset .md-button{border:.1rem solid;border-radius:.1rem;color:var(--md-primary-fg-color);cursor:pointer;display:inline-block;font-weight:700;padding:.625em 2em;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[dir=ltr] .md-typeset .md-input{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .md-input,[dir=rtl] .md-typeset .md-input{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .md-input{border-top-left-radius:.1rem}.md-typeset .md-input{border-bottom:.1rem solid var(--md-default-fg-color--lighter);box-shadow:var(--md-shadow-z1);font-size:.8rem;height:1.8rem;padding:0 .6rem;transition:border .25s,box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input--stretch{width:100%}.md-header{background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem #0000,0 .2rem .4rem #0000;color:var(--md-primary-bg-color);display:block;left:0;position:sticky;right:0;top:0;z-index:4}@media print{.md-header{display:none}}.md-header[hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header--shadow{box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header__inner{align-items:center;display:flex;padding:0 .2rem}.md-header__button{color:currentcolor;cursor:pointer;margin:.2rem;outline-color:var(--md-accent-fg-color);padding:.4rem;position:relative;transition:opacity .25s;vertical-align:middle;z-index:1}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.234375em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{fill:currentcolor;display:block;height:1.2rem;width:auto}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{display:flex;max-width:100%;position:absolute;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;white-space:nowrap}.md-header__topic+.md-header__topic{opacity:0;pointer-events:none;transform:translateX(1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__topic:first-child{font-weight:700}[dir=ltr] .md-header__title{margin-left:1rem;margin-right:.4rem}[dir=rtl] .md-header__title{margin-left:.4rem;margin-right:1rem}.md-header__title{flex-grow:1;font-size:.9rem;height:2.4rem;line-height:2.4rem}.md-header__title--active .md-header__topic{opacity:0;pointer-events:none;transform:translateX(-1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__title--active .md-header__topic{transform:translateX(1.25rem)}.md-header__title--active .md-header__topic+.md-header__topic{opacity:1;pointer-events:auto;transform:translateX(0);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;z-index:0}.md-header__title>.md-header__ellipsis{height:100%;position:relative;width:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;transition:max-width 0ms .25s,opacity .25s .25s;white-space:nowrap}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__option>input{bottom:0}.md-header__source{display:none}@media screen and (min-width:60em){[dir=ltr] .md-header__source{margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem}.md-header__source{display:block;max-width:11.7rem;width:11.7rem}}@media screen and (min-width:76.25em){[dir=ltr] .md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-meta{color:var(--md-default-fg-color--light);font-size:.7rem;line-height:1.3}.md-meta__list{display:inline-flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}.md-meta__item:not(:last-child):after{content:"·";margin-left:.2rem;margin-right:.2rem}.md-meta__link{color:var(--md-typeset-a-color)}.md-meta__link:focus,.md-meta__link:hover{color:var(--md-accent-fg-color)}.md-draft{background-color:#ff1744;border-radius:.125em;color:#fff;display:inline-block;font-weight:700;padding-left:.5714285714em;padding-right:.5714285714em}:root{--md-nav-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-nav-icon--next:url('data:image/svg+xml;charset=utf-8,');--md-toc-icon:url('data:image/svg+xml;charset=utf-8,')}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{color:var(--md-default-fg-color--light);display:block;font-weight:700;overflow:hidden;padding:0 .6rem;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{height:100%;width:auto}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__list{list-style:none;margin:0;padding:0}.md-nav__link{align-items:flex-start;display:flex;gap:.4rem;margin-top:.625em;scroll-snap-align:start;transition:color 125ms}.md-nav__link--passed{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active,.md-nav__item .md-nav__link--active code{color:var(--md-typeset-a-color)}.md-nav__link .md-ellipsis{position:relative}[dir=ltr] .md-nav__link .md-icon:last-child{margin-left:auto}[dir=rtl] .md-nav__link .md-icon:last-child{margin-right:auto}.md-nav__link svg{fill:currentcolor;flex-shrink:0;height:1.3em;position:relative}.md-nav__link[for]:focus,.md-nav__link[for]:hover,.md-nav__link[href]:focus,.md-nav__link[href]:hover{color:var(--md-accent-fg-color);cursor:pointer}.md-nav__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{background-color:currentcolor;display:block;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);width:100%}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__container>.md-nav__link{margin-top:0}.md-nav__container>.md-nav__link:first-child{flex-grow:1;min-width:0}.md-nav__icon{flex-shrink:0}.md-nav__source{display:none}@media screen and (max-width:76.234375em){.md-nav--primary,.md-nav--primary .md-nav{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;height:100%;left:0;position:absolute;right:0;top:0;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);cursor:pointer;height:5.6rem;line-height:2.4rem;padding:3rem .8rem .2rem;position:relative;white-space:nowrap}[dir=ltr] .md-nav--primary .md-nav__title .md-nav__icon{left:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem}.md-nav--primary .md-nav__title .md-nav__icon{display:block;height:1.2rem;margin:.2rem;position:absolute;top:.4rem;width:1.2rem}.md-nav--primary .md-nav__title .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}.md-nav--primary .md-nav__title~.md-nav__list{background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;overflow-y:auto;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);font-weight:700}.md-nav--primary .md-nav__title .md-logo{display:block;left:.2rem;margin:.2rem;padding:.4rem;position:absolute;right:.2rem;top:.2rem}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link svg{margin-top:.1em}.md-nav--primary .md-nav__link>.md-nav__link{padding:0}[dir=ltr] .md-nav--primary .md-nav__link .md-nav__icon{margin-right:-.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{margin-left:-.2rem}.md-nav--primary .md-nav__link .md-nav__icon{font-size:1.2rem;height:1.2rem;width:1.2rem}.md-nav--primary .md-nav__link .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav{background-color:initial;position:static}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;opacity:0;transform:translateX(100%);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{opacity:1;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.984375em){.md-nav--primary .md-nav__link[for=__toc]{display:flex}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{background-color:var(--md-primary-fg-color--dark);color:var(--md-primary-bg-color);display:block;padding:0 .2rem}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-nav--integrated .md-nav__link[for=__toc]{display:flex}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav{margin-bottom:-.4rem}.md-nav--secondary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--secondary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--secondary .md-nav__list{padding-right:.6rem}.md-nav--secondary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--secondary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--secondary .md-nav__item>.md-nav__link{margin-left:.4rem}}@media screen and (min-width:76.25em){.md-nav{margin-bottom:-.4rem;transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--primary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--primary .md-nav__list{padding-right:.6rem}.md-nav--primary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--primary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--primary .md-nav__item>.md-nav__link{margin-left:.4rem}.md-nav__toggle~.md-nav{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .25s,visibility 0ms .25s;visibility:collapse}.md-nav__toggle~.md-nav>.md-nav__list{overflow:hidden}.md-nav__toggle.md-toggle--indeterminate~.md-nav,.md-nav__toggle:checked~.md-nav{grid-template-rows:1fr;opacity:1;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .15s .1s,visibility 0ms;visibility:visible}.md-nav__toggle.md-toggle--indeterminate~.md-nav{transition:none}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{font-weight:700}.md-nav__item--section>.md-nav__link[for]{color:var(--md-default-fg-color--light)}.md-nav__item--section>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav__item--section>.md-nav__link .md-icon,.md-nav__item--section>.md-nav__link>[for]{display:none}[dir=ltr] .md-nav__item--section>.md-nav{margin-left:-.6rem}[dir=rtl] .md-nav__item--section>.md-nav{margin-right:-.6rem}.md-nav__item--section>.md-nav{display:block;opacity:1;visibility:visible}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{border-radius:100%;height:.9rem;transition:background-color .25s;width:.9rem}.md-nav__icon:hover{background-color:var(--md-accent-fg-color--transparent)}.md-nav__icon:after{background-color:currentcolor;border-radius:100%;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:transform .25s;vertical-align:-.1rem;width:100%}[dir=rtl] .md-nav__icon:after{transform:rotate(180deg)}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon:after,.md-nav__item--nested .md-toggle--indeterminate~.md-nav__link .md-nav__icon:after{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);margin-top:0;position:sticky;top:0;z-index:1}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active.md-nav__item--section{margin:0}[dir=ltr] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-left:-.6rem}[dir=rtl] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-right:-.6rem}.md-nav--lifted>.md-nav__list>.md-nav__item>[for]{color:var(--md-default-fg-color--light)}.md-nav--lifted .md-nav[data-md-level="1"]{grid-template-rows:1fr;opacity:1;visibility:visible}[dir=ltr] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-left:.05rem solid var(--md-primary-fg-color)}[dir=rtl] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-right:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{display:block;margin-bottom:1.25em;opacity:1;visibility:visible}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__list{overflow:visible;padding-bottom:0}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__title{display:none}}.md-pagination{font-size:.8rem;font-weight:700;gap:.4rem}.md-pagination,.md-pagination>*{align-items:center;display:flex;justify-content:center}.md-pagination>*{border-radius:.2rem;height:1.8rem;min-width:1.8rem;text-align:center}.md-pagination__current{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light)}.md-pagination__link{transition:color 125ms,background-color 125ms}.md-pagination__link:focus,.md-pagination__link:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-pagination__link:focus svg,.md-pagination__link:hover svg{color:var(--md-accent-fg-color)}.md-pagination__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-pagination__link svg{fill:currentcolor;color:var(--md-default-fg-color--lighter);display:block;max-height:100%;width:1.2rem}.md-post__back{border-bottom:.05rem solid var(--md-default-fg-color--lightest);margin-bottom:1.2rem;padding-bottom:1.2rem}@media screen and (max-width:76.234375em){.md-post__back{display:none}}[dir=rtl] .md-post__back svg{transform:scaleX(-1)}.md-post__authors{display:flex;flex-direction:column;gap:.6rem;margin:0 .6rem 1.2rem}.md-post .md-post__meta a{transition:color 125ms}.md-post .md-post__meta a:focus,.md-post .md-post__meta a:hover{color:var(--md-accent-fg-color)}.md-post__title{color:var(--md-default-fg-color--light);font-weight:700}.md-post--excerpt{margin-bottom:3.2rem}.md-post--excerpt .md-post__header{align-items:center;display:flex;gap:.6rem;min-height:1.6rem}.md-post--excerpt .md-post__authors{align-items:center;display:inline-flex;flex-direction:row;gap:.2rem;margin:0;min-height:2.4rem}[dir=ltr] .md-post--excerpt .md-post__meta .md-meta__list{margin-right:.4rem}[dir=rtl] .md-post--excerpt .md-post__meta .md-meta__list{margin-left:.4rem}.md-post--excerpt .md-post__content>:first-child{--md-scroll-margin:6rem;margin-top:0}.md-post>.md-nav--secondary{margin:1em 0}.md-profile{align-items:center;display:flex;font-size:.7rem;gap:.6rem;line-height:1.4;width:100%}.md-profile__description{flex-grow:1}.md-content--post{display:flex}@media screen and (max-width:76.234375em){.md-content--post{flex-flow:column-reverse}}.md-content--post>.md-content__inner{min-width:0}@media screen and (min-width:76.25em){[dir=ltr] .md-content--post>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-content--post>.md-content__inner{margin-right:1.2rem}}@media screen and (max-width:76.234375em){.md-sidebar.md-sidebar--post{padding:0;position:static;width:100%}.md-sidebar.md-sidebar--post .md-sidebar__scrollwrap{overflow:visible}.md-sidebar.md-sidebar--post .md-sidebar__inner{padding:0}.md-sidebar.md-sidebar--post .md-post__meta{margin-left:.6rem;margin-right:.6rem}.md-sidebar.md-sidebar--post .md-nav__item{border:none;display:inline}.md-sidebar.md-sidebar--post .md-nav__list{display:inline-flex;flex-wrap:wrap;gap:.6rem;padding-bottom:.6rem;padding-top:.6rem}.md-sidebar.md-sidebar--post .md-nav__link{padding:0}.md-sidebar.md-sidebar--post .md-nav{height:auto;margin-bottom:0;position:static}}:root{--md-progress-value:0;--md-progress-delay:400ms}.md-progress{background:var(--md-primary-bg-color);height:.075rem;opacity:min(clamp(0,var(--md-progress-value),1),clamp(0,100 - var(--md-progress-value),1));position:fixed;top:0;transform:scaleX(calc(var(--md-progress-value)*1%));transform-origin:left;transition:transform .5s cubic-bezier(.19,1,.22,1),opacity .25s var(--md-progress-delay);width:100%;z-index:4}:root{--md-search-result-icon:url('data:image/svg+xml;charset=utf-8,')}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__overlay{left:-2.2rem}[dir=rtl] .md-search__overlay{right:-2.2rem}.md-search__overlay{background-color:var(--md-default-bg-color);border-radius:1rem;height:2rem;overflow:hidden;pointer-events:none;position:absolute;top:-1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;width:2rem}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){[dir=ltr] .md-search__overlay{left:0}[dir=rtl] .md-search__overlay{right:0}.md-search__overlay{background-color:#0000008a;cursor:pointer;height:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0}[data-md-toggle=search]:checked~.md-header .md-search__overlay{height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@media screen and (max-width:29.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__inner{left:0}[dir=rtl] .md-search__inner{right:0}.md-search__inner{height:0;opacity:0;overflow:hidden;position:fixed;top:0;transform:translateX(5%);transition:width 0ms .3s,height 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;width:0;z-index:2}[dir=rtl] .md-search__inner{transform:translateX(-5%)}[data-md-toggle=search]:checked~.md-header .md-search__inner{height:100%;opacity:1;transform:translateX(0);transition:width 0ms 0ms,height 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__inner{float:right}[dir=rtl] .md-search__inner{float:left}.md-search__inner{padding:.1rem 0;position:relative;transition:width .25s cubic-bezier(.1,.7,.1,1);width:11.7rem}}@media screen and (min-width:60em) and (max-width:76.234375em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem #0000;height:2.4rem;position:relative;transition:color .25s,background-color .25s;z-index:2}@media screen and (min-width:60em){.md-search__form{background-color:#00000042;border-radius:.1rem;height:1.8rem}.md-search__form:hover{background-color:#ffffff1f}}[data-md-toggle=search]:checked~.md-header .md-search__form{background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0;box-shadow:0 0 .6rem #00000012;color:var(--md-default-fg-color)}[dir=ltr] .md-search__input{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__input{padding-left:2.2rem;padding-right:3.6rem}.md-search__input{background:#0000;font-size:.9rem;height:100%;position:relative;text-overflow:ellipsis;width:100%;z-index:2}.md-search__input::placeholder{transition:color .25s}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}@media screen and (max-width:59.984375em){.md-search__input{font-size:.9rem;height:2.4rem;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__input{padding-left:2.2rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input{color:inherit;font-size:.8rem}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}[data-md-toggle=search]:checked~.md-header .md-search__input{text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:#0000}}.md-search__icon{cursor:pointer;display:inline-block;height:1.2rem;transition:color .25s,opacity .25s;width:1.2rem}.md-search__icon:hover{opacity:.7}[dir=ltr] .md-search__icon[for=__search]{left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem}.md-search__icon[for=__search]{position:absolute;top:.3rem;z-index:2}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__icon[for=__search]{left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem}.md-search__icon[for=__search]{top:.6rem}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}[dir=ltr] .md-search__options{right:.5rem}[dir=rtl] .md-search__options{left:.5rem}.md-search__options{pointer-events:none;position:absolute;top:.3rem;z-index:2}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__options{right:.8rem}[dir=rtl] .md-search__options{left:.8rem}.md-search__options{top:.6rem}}[dir=ltr] .md-search__options>.md-icon{margin-left:.2rem}[dir=rtl] .md-search__options>.md-icon{margin-right:.2rem}.md-search__options>.md-icon{color:var(--md-default-fg-color--light);opacity:0;transform:scale(.75);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-search__options>.md-icon:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon{opacity:1;pointer-events:auto;transform:scale(1)}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon:hover{opacity:.7}[dir=ltr] .md-search__suggest{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__suggest{padding-left:2.2rem;padding-right:3.6rem}.md-search__suggest{align-items:center;color:var(--md-default-fg-color--lighter);display:flex;font-size:.9rem;height:100%;opacity:0;position:absolute;top:0;transition:opacity 50ms;white-space:nowrap;width:100%}@media screen and (min-width:60em){[dir=ltr] .md-search__suggest{padding-left:2.2rem}[dir=rtl] .md-search__suggest{padding-right:2.2rem}.md-search__suggest{font-size:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__suggest{opacity:1;transition:opacity .3s .1s}[dir=ltr] .md-search__output{border-bottom-left-radius:.1rem}[dir=ltr] .md-search__output,[dir=rtl] .md-search__output{border-bottom-right-radius:.1rem}[dir=rtl] .md-search__output{border-bottom-left-radius:.1rem}.md-search__output{overflow:hidden;position:absolute;width:100%;z-index:1}@media screen and (max-width:59.984375em){.md-search__output{bottom:0;top:2.4rem}}@media screen and (min-width:60em){.md-search__output{opacity:0;top:1.9rem;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:var(--md-shadow-z3);opacity:1}}.md-search__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);height:100%;overflow-y:auto;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1),(max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-search__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;padding:0 .8rem;scroll-snap-align:start}@media screen and (min-width:60em){[dir=ltr] .md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem}}.md-search-result__list{list-style:none;margin:0;padding:0;-webkit-user-select:none;user-select:none}.md-search-result__item{box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;scroll-snap-align:start;transition:background-color .25s}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more>summary{cursor:pointer;display:block;outline:none;position:sticky;scroll-snap-align:start;top:0;z-index:1}.md-search-result__more>summary::marker{display:none}.md-search-result__more>summary::-webkit-details-marker{display:none}.md-search-result__more>summary>div{color:var(--md-typeset-a-color);font-size:.64rem;padding:.75em .8rem;transition:color .25s,background-color .25s}@media screen and (min-width:60em){[dir=ltr] .md-search-result__more>summary>div{padding-left:2.2rem}[dir=rtl] .md-search-result__more>summary>div{padding-right:2.2rem}}.md-search-result__more>summary:focus>div,.md-search-result__more>summary:hover>div{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more[open]>summary{background-color:var(--md-default-bg-color)}.md-search-result__article{overflow:hidden;padding:0 .8rem;position:relative}@media screen and (min-width:60em){[dir=ltr] .md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem}}[dir=ltr] .md-search-result__icon{left:0}[dir=rtl] .md-search-result__icon{right:0}.md-search-result__icon{color:var(--md-default-fg-color--light);height:1.2rem;margin:.5rem;position:absolute;width:1.2rem}@media screen and (max-width:59.984375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result .md-typeset{color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6}.md-search-result .md-typeset h1{color:var(--md-default-fg-color);font-size:.8rem;font-weight:400;line-height:1.4;margin:.55rem 0}.md-search-result .md-typeset h1 mark{text-decoration:none}.md-search-result .md-typeset h2{color:var(--md-default-fg-color);font-size:.64rem;font-weight:700;line-height:1.6;margin:.5em 0}.md-search-result .md-typeset h2 mark{text-decoration:none}.md-search-result__terms{color:var(--md-default-fg-color);display:block;font-size:.64rem;font-style:italic;margin:.5em 0}.md-search-result mark{background-color:initial;color:var(--md-accent-fg-color);text-decoration:underline}.md-select{position:relative;z-index:1}.md-select__inner{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:50%;margin-top:.2rem;max-height:0;opacity:0;position:absolute;top:calc(100% - .2rem);transform:translate3d(-50%,.3rem,0);transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select__inner:after{border-bottom:.2rem solid #0000;border-bottom-color:var(--md-default-bg-color);border-left:.2rem solid #0000;border-right:.2rem solid #0000;border-top:0;content:"";height:0;left:50%;margin-left:-.2rem;margin-top:-.2rem;position:absolute;top:0;width:0}.md-select__list{border-radius:.1rem;font-size:.8rem;list-style-type:none;margin:0;max-height:inherit;overflow:auto;padding:0}.md-select__item{line-height:1.8rem}[dir=ltr] .md-select__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-select__link{padding-left:1.2rem;padding-right:.6rem}.md-select__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:background-color .25s,color .25s;width:100%}.md-select__link:focus,.md-select__link:hover{color:var(--md-accent-fg-color)}.md-select__link:focus{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{align-self:flex-start;flex-shrink:0;padding:1.2rem 0;position:sticky;top:2.4rem;width:12.1rem}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.234375em){[dir=ltr] .md-sidebar--primary{left:-12.1rem}[dir=rtl] .md-sidebar--primary{right:-12.1rem}.md-sidebar--primary{background-color:var(--md-default-bg-color);display:block;height:100%;position:fixed;top:0;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;width:12.1rem;z-index:5}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:var(--md-shadow-z3);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{bottom:0;left:0;margin:0;overflow:hidden;position:absolute;right:0;scroll-snap-type:none;top:0}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}.md-header--lifted~.md-container .md-sidebar{top:4.8rem}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{scrollbar-gutter:stable;-webkit-backface-visibility:hidden;backface-visibility:hidden;margin:0 .2rem;overflow-y:auto;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}.md-sidebar__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-sidebar__scrollwrap:focus-within,.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb:hover,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@supports selector(::-webkit-scrollbar){.md-sidebar__scrollwrap{scrollbar-gutter:auto}[dir=ltr] .md-sidebar__inner{padding-right:calc(100% - 11.5rem)}[dir=rtl] .md-sidebar__inner{padding-left:calc(100% - 11.5rem)}}@media screen and (max-width:76.234375em){.md-overlay{background-color:#0000008a;height:0;opacity:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0;z-index:5}[data-md-toggle=drawer]:checked~.md-overlay{height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@keyframes facts{0%{height:0}to{height:.65rem}}@keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}:root{--md-source-forks-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-repositories-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-stars-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-source{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.65rem;line-height:1.2;outline-color:var(--md-accent-fg-color);transition:opacity .25s;white-space:nowrap}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;height:2.4rem;vertical-align:middle;width:2rem}[dir=ltr] .md-source__icon svg{margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem}.md-source__icon svg{margin-top:.6rem}[dir=ltr] .md-source__icon+.md-source__repository{padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{padding-right:2rem}[dir=ltr] .md-source__icon+.md-source__repository{margin-left:-2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem}[dir=ltr] .md-source__repository{margin-left:.6rem}[dir=rtl] .md-source__repository{margin-right:.6rem}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{display:flex;font-size:.55rem;gap:.4rem;list-style-type:none;margin:.1rem 0 0;opacity:.75;overflow:hidden;padding:0;width:100%}.md-source__repository--active .md-source__facts{animation:facts .25s ease-in}.md-source__fact{overflow:hidden;text-overflow:ellipsis}.md-source__repository--active .md-source__fact{animation:fact .4s ease-out}[dir=ltr] .md-source__fact:before{margin-right:.1rem}[dir=rtl] .md-source__fact:before{margin-left:.1rem}.md-source__fact:before{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-top;width:.6rem}.md-source__fact:nth-child(1n+2){flex-shrink:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-source-file{margin:1em 0}[dir=ltr] .md-source-file__fact{margin-right:.6rem}[dir=rtl] .md-source-file__fact{margin-left:.6rem}.md-source-file__fact{align-items:center;color:var(--md-default-fg-color--light);display:inline-flex;font-size:.68rem;gap:.3rem}.md-source-file__fact .md-icon{flex-shrink:0;margin-bottom:.05rem}[dir=ltr] .md-source-file__fact .md-author{float:left}[dir=rtl] .md-source-file__fact .md-author{float:right}.md-source-file__fact .md-author{margin-right:.2rem}.md-source-file__fact svg{width:.9rem}:root{--md-status:url('data:image/svg+xml;charset=utf-8,');--md-status--new:url('data:image/svg+xml;charset=utf-8,');--md-status--deprecated:url('data:image/svg+xml;charset=utf-8,');--md-status--encrypted:url('data:image/svg+xml;charset=utf-8,')}.md-status:after{background-color:var(--md-default-fg-color--light);content:"";display:inline-block;height:1.125em;-webkit-mask-image:var(--md-status);mask-image:var(--md-status);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-bottom;width:1.125em}.md-status:hover:after{background-color:currentcolor}.md-status--new:after{-webkit-mask-image:var(--md-status--new);mask-image:var(--md-status--new)}.md-status--deprecated:after{-webkit-mask-image:var(--md-status--deprecated);mask-image:var(--md-status--deprecated)}.md-status--encrypted:after{-webkit-mask-image:var(--md-status--encrypted);mask-image:var(--md-status--encrypted)}.md-tabs{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);display:block;line-height:1.3;overflow:auto;width:100%;z-index:3}@media print{.md-tabs{display:none}}@media screen and (max-width:76.234375em){.md-tabs{display:none}}.md-tabs[hidden]{pointer-events:none}[dir=ltr] .md-tabs__list{margin-left:.2rem}[dir=rtl] .md-tabs__list{margin-right:.2rem}.md-tabs__list{contain:content;display:flex;list-style:none;margin:0;overflow:auto;padding:0;scrollbar-width:none;white-space:nowrap}.md-tabs__list::-webkit-scrollbar{display:none}.md-tabs__item{height:2.4rem;padding-left:.6rem;padding-right:.6rem}.md-tabs__item--active .md-tabs__link{color:inherit;opacity:1}.md-tabs__link{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:flex;font-size:.7rem;margin-top:.8rem;opacity:.7;outline-color:var(--md-accent-fg-color);outline-offset:.2rem;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}[dir=ltr] .md-tabs__link svg{margin-right:.4rem}[dir=rtl] .md-tabs__link svg{margin-left:.4rem}.md-tabs__link svg{fill:currentcolor;height:1.3em}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[hidden] .md-tabs__link{opacity:0;transform:translateY(50%);transition:transform 0ms .1s,opacity .1s}:root{--md-tag-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .md-tags:not([hidden]){display:inline-flex;flex-wrap:wrap;gap:.5em;margin-bottom:.75em;margin-top:-.125em}.md-typeset .md-tag{align-items:center;background:var(--md-default-fg-color--lightest);border-radius:2.4rem;display:inline-flex;font-size:.64rem;font-size:min(.8em,.64rem);font-weight:700;gap:.5em;letter-spacing:normal;line-height:1.6;padding:.3125em .78125em}.md-typeset .md-tag[href]{-webkit-tap-highlight-color:transparent;color:inherit;outline:none;transition:color 125ms,background-color 125ms}.md-typeset .md-tag[href]:focus,.md-typeset .md-tag[href]:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[id]>.md-typeset .md-tag{vertical-align:text-top}.md-typeset .md-tag-icon:before{background-color:var(--md-default-fg-color--lighter);content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-tag-icon);mask-image:var(--md-tag-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset .md-tag-icon[href]:focus:before,.md-typeset .md-tag-icon[href]:hover:before{background-color:var(--md-accent-bg-color)}@keyframes pulse{0%{transform:scale(.95)}75%{transform:scale(1)}to{transform:scale(.95)}}:root{--md-annotation-bg-icon:url('data:image/svg+xml;charset=utf-8,');--md-annotation-icon:url('data:image/svg+xml;charset=utf-8,')}.md-tooltip{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);font-family:var(--md-text-font-family);left:clamp(var(--md-tooltip-0,0rem) + .8rem,var(--md-tooltip-x),100vw + var(--md-tooltip-0,0rem) + .8rem - var(--md-tooltip-width) - 2 * .8rem);max-width:calc(100vw - 1.6rem);opacity:0;position:absolute;top:var(--md-tooltip-y);transform:translateY(-.4rem);transition:transform 0ms .25s,opacity .25s,z-index .25s;width:var(--md-tooltip-width);z-index:0}.md-tooltip--active{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip--inline{font-weight:700;-webkit-user-select:none;user-select:none;width:auto}.md-tooltip--inline:not(.md-tooltip--active){transform:translateY(.2rem) scale(.9)}.md-tooltip--inline .md-tooltip__inner{font-size:.5rem;padding:.2rem .4rem}[hidden]+.md-tooltip--inline{display:none}.focus-visible>.md-tooltip,.md-tooltip:target{outline:var(--md-accent-fg-color) auto}.md-tooltip__inner{font-size:.64rem;padding:.8rem}.md-tooltip__inner.md-typeset>:first-child{margin-top:0}.md-tooltip__inner.md-typeset>:last-child{margin-bottom:0}.md-annotation{font-style:normal;font-weight:400;outline:none;text-align:initial;vertical-align:text-bottom;white-space:normal}[dir=rtl] .md-annotation{direction:rtl}code .md-annotation{font-family:var(--md-code-font-family);font-size:inherit}.md-annotation:not([hidden]){display:inline-block;line-height:1.25}.md-annotation__index{border-radius:.01px;cursor:pointer;display:inline-block;margin-left:.4ch;margin-right:.4ch;outline:none;overflow:hidden;position:relative;-webkit-user-select:none;user-select:none;vertical-align:text-top;z-index:0}.md-annotation .md-annotation__index{transition:z-index .25s}@media screen{.md-annotation__index{width:2.2ch}[data-md-visible]>.md-annotation__index{animation:pulse 2s infinite}.md-annotation__index:before{background:var(--md-default-bg-color);-webkit-mask-image:var(--md-annotation-bg-icon);mask-image:var(--md-annotation-bg-icon)}.md-annotation__index:after,.md-annotation__index:before{content:"";height:2.2ch;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:-.1ch;width:2.2ch;z-index:-1}.md-annotation__index:after{background-color:var(--md-default-fg-color--lighter);-webkit-mask-image:var(--md-annotation-icon);mask-image:var(--md-annotation-icon);transform:scale(1.0001);transition:background-color .25s,transform .25s}.md-tooltip--active+.md-annotation__index:after{transform:rotate(45deg)}.md-tooltip--active+.md-annotation__index:after,:hover>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}}.md-tooltip--active+.md-annotation__index{animation-play-state:paused;transition-duration:0ms;z-index:2}.md-annotation__index [data-md-annotation-id]{display:inline-block}@media print{.md-annotation__index [data-md-annotation-id]{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);font-weight:700;padding:0 .6ch;white-space:nowrap}.md-annotation__index [data-md-annotation-id]:after{content:attr(data-md-annotation-id)}}.md-typeset .md-annotation-list{counter-reset:xxx;list-style:none}.md-typeset .md-annotation-list li{position:relative}[dir=ltr] .md-typeset .md-annotation-list li:before{left:-2.125em}[dir=rtl] .md-typeset .md-annotation-list li:before{right:-2.125em}.md-typeset .md-annotation-list li:before{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);content:counter(xxx);counter-increment:xxx;font-size:.8875em;font-weight:700;height:2ch;line-height:1.25;min-width:2ch;padding:0 .6ch;position:absolute;text-align:center;top:.25em}:root{--md-tooltip-width:20rem;--md-tooltip-tail:0.3rem}.md-tooltip2{-webkit-backface-visibility:hidden;backface-visibility:hidden;color:var(--md-default-fg-color);font-family:var(--md-text-font-family);opacity:0;pointer-events:none;position:absolute;top:calc(var(--md-tooltip-host-y) + var(--md-tooltip-y));transform:translateY(-.4rem);transform-origin:calc(var(--md-tooltip-host-x) + var(--md-tooltip-x)) 0;transition:transform 0ms .25s,opacity .25s,z-index .25s;width:100%;z-index:0}.md-tooltip2:before{border-left:var(--md-tooltip-tail) solid #0000;border-right:var(--md-tooltip-tail) solid #0000;content:"";display:block;left:clamp(1.5 * .8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-tail),100vw - 2 * var(--md-tooltip-tail) - 1.5 * .8rem);position:absolute;z-index:1}.md-tooltip2--top:before{border-top:var(--md-tooltip-tail) solid var(--md-default-bg-color);bottom:calc(var(--md-tooltip-tail)*-1 + .025rem);filter:drop-shadow(0 1px 0 hsla(0,0%,0%,.05))}.md-tooltip2--bottom:before{border-bottom:var(--md-tooltip-tail) solid var(--md-default-bg-color);filter:drop-shadow(0 -1px 0 hsla(0,0%,0%,.05));top:calc(var(--md-tooltip-tail)*-1 + .025rem)}.md-tooltip2--active{opacity:1;transform:translateY(0);transition:transform .4s cubic-bezier(0,1,.5,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip2__inner{scrollbar-gutter:stable;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);left:clamp(.8rem,var(--md-tooltip-host-x) - .8rem,100vw - var(--md-tooltip-width) - .8rem);max-height:40vh;max-width:calc(100vw - 1.6rem);position:relative;scrollbar-width:thin}.md-tooltip2__inner::-webkit-scrollbar{height:.2rem;width:.2rem}.md-tooltip2__inner::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-tooltip2__inner::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}[role=tooltip]>.md-tooltip2__inner{font-size:.5rem;font-weight:700;left:clamp(.8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-width)/2,100vw - var(--md-tooltip-width) - .8rem);max-width:min(100vw - 2 * .8rem,400px);padding:.2rem .4rem;-webkit-user-select:none;user-select:none;width:-moz-fit-content;width:fit-content}.md-tooltip2__inner.md-typeset>:first-child{margin-top:0}.md-tooltip2__inner.md-typeset>:last-child{margin-bottom:0}[dir=ltr] .md-top{margin-left:50%}[dir=rtl] .md-top{margin-right:50%}.md-top{background-color:var(--md-default-bg-color);border-radius:1.6rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color--light);cursor:pointer;display:block;font-size:.7rem;outline:none;padding:.4rem .8rem;position:fixed;top:3.2rem;transform:translate(-50%);transition:color 125ms,background-color 125ms,transform 125ms cubic-bezier(.4,0,.2,1),opacity 125ms;z-index:2}@media print{.md-top{display:none}}[dir=rtl] .md-top{transform:translate(50%)}.md-top[hidden]{opacity:0;pointer-events:none;transform:translate(-50%,.2rem);transition-duration:0ms}[dir=rtl] .md-top[hidden]{transform:translate(50%,.2rem)}.md-top:focus,.md-top:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top svg{display:inline-block;vertical-align:-.5em}@keyframes hoverfix{0%{pointer-events:none}}:root{--md-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-version{flex-shrink:0;font-size:.8rem;height:2.4rem}[dir=ltr] .md-version__current{margin-left:1.4rem;margin-right:.4rem}[dir=rtl] .md-version__current{margin-left:.4rem;margin-right:1.4rem}.md-version__current{color:inherit;cursor:pointer;outline:none;position:relative;top:.05rem}[dir=ltr] .md-version__current:after{margin-left:.4rem}[dir=rtl] .md-version__current:after{margin-right:.4rem}.md-version__current:after{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.4rem}.md-version__alias{margin-left:.3rem;opacity:.7}.md-version__list{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);list-style-type:none;margin:.2rem .8rem;max-height:0;opacity:0;overflow:auto;padding:0;position:absolute;scroll-snap-type:y mandatory;top:.15rem;transition:max-height 0ms .5s,opacity .25s .25s;z-index:3}.md-version:focus-within .md-version__list,.md-version:hover .md-version__list{max-height:10rem;opacity:1;transition:max-height 0ms,opacity .25s}@media (hover:none),(pointer:coarse){.md-version:hover .md-version__list{animation:hoverfix .25s forwards}.md-version:focus-within .md-version__list{animation:none}}.md-version__item{line-height:1.8rem}[dir=ltr] .md-version__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-version__link{padding-left:1.2rem;padding-right:.6rem}.md-version__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:color .25s,background-color .25s;white-space:nowrap;width:100%}.md-version__link:focus,.md-version__link:hover{color:var(--md-accent-fg-color)}.md-version__link:focus{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--abstract:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--info:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--tip:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--success:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--question:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--warning:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--failure:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--danger:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--bug:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--example:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--quote:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .admonition,.md-typeset details{background-color:var(--md-admonition-bg-color);border:.075rem solid #448aff;border-radius:.2rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid;transition:box-shadow 125ms}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}.md-typeset .admonition:focus-within,.md-typeset details:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .admonition>*,.md-typeset details>*{box-sizing:border-box}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-bottom:1em;margin-top:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{padding-left:2rem;padding-right:.6rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{padding-left:.6rem;padding-right:2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-left-width:.2rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-right-width:.2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset .admonition-title,.md-typeset summary{background-color:#448aff1a;border:none;font-weight:700;margin:0 -.6rem;padding-bottom:.4rem;padding-top:.4rem;position:relative}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}[dir=ltr] .md-typeset .admonition-title:before,[dir=ltr] .md-typeset summary:before{left:.6rem}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem}.md-typeset .admonition-title:before,.md-typeset summary:before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset .admonition-title code,.md-typeset summary code{box-shadow:0 0 0 .05rem var(--md-default-fg-color--lightest)}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .admonition.note:focus-within,.md-typeset details.note:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:#448aff1a}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note)}.md-typeset .note>.admonition-title:after,.md-typeset .note>summary:after{color:#448aff}.md-typeset .admonition.abstract,.md-typeset details.abstract{border-color:#00b0ff}.md-typeset .admonition.abstract:focus-within,.md-typeset details.abstract:focus-within{box-shadow:0 0 0 .2rem #00b0ff1a}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary{background-color:#00b0ff1a}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract)}.md-typeset .abstract>.admonition-title:after,.md-typeset .abstract>summary:after{color:#00b0ff}.md-typeset .admonition.info,.md-typeset details.info{border-color:#00b8d4}.md-typeset .admonition.info:focus-within,.md-typeset details.info:focus-within{box-shadow:0 0 0 .2rem #00b8d41a}.md-typeset .info>.admonition-title,.md-typeset .info>summary{background-color:#00b8d41a}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info)}.md-typeset .info>.admonition-title:after,.md-typeset .info>summary:after{color:#00b8d4}.md-typeset .admonition.tip,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .admonition.tip:focus-within,.md-typeset details.tip:focus-within{box-shadow:0 0 0 .2rem #00bfa51a}.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:#00bfa51a}.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip)}.md-typeset .tip>.admonition-title:after,.md-typeset .tip>summary:after{color:#00bfa5}.md-typeset .admonition.success,.md-typeset details.success{border-color:#00c853}.md-typeset .admonition.success:focus-within,.md-typeset details.success:focus-within{box-shadow:0 0 0 .2rem #00c8531a}.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:#00c8531a}.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success)}.md-typeset .success>.admonition-title:after,.md-typeset .success>summary:after{color:#00c853}.md-typeset .admonition.question,.md-typeset details.question{border-color:#64dd17}.md-typeset .admonition.question:focus-within,.md-typeset details.question:focus-within{box-shadow:0 0 0 .2rem #64dd171a}.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:#64dd171a}.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question)}.md-typeset .question>.admonition-title:after,.md-typeset .question>summary:after{color:#64dd17}.md-typeset .admonition.warning,.md-typeset details.warning{border-color:#ff9100}.md-typeset .admonition.warning:focus-within,.md-typeset details.warning:focus-within{box-shadow:0 0 0 .2rem #ff91001a}.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:#ff91001a}.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning)}.md-typeset .warning>.admonition-title:after,.md-typeset .warning>summary:after{color:#ff9100}.md-typeset .admonition.failure,.md-typeset details.failure{border-color:#ff5252}.md-typeset .admonition.failure:focus-within,.md-typeset details.failure:focus-within{box-shadow:0 0 0 .2rem #ff52521a}.md-typeset .failure>.admonition-title,.md-typeset .failure>summary{background-color:#ff52521a}.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure)}.md-typeset .failure>.admonition-title:after,.md-typeset .failure>summary:after{color:#ff5252}.md-typeset .admonition.danger,.md-typeset details.danger{border-color:#ff1744}.md-typeset .admonition.danger:focus-within,.md-typeset details.danger:focus-within{box-shadow:0 0 0 .2rem #ff17441a}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary{background-color:#ff17441a}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger)}.md-typeset .danger>.admonition-title:after,.md-typeset .danger>summary:after{color:#ff1744}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .admonition.bug:focus-within,.md-typeset details.bug:focus-within{box-shadow:0 0 0 .2rem #f500571a}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:#f500571a}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug)}.md-typeset .bug>.admonition-title:after,.md-typeset .bug>summary:after{color:#f50057}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .admonition.example:focus-within,.md-typeset details.example:focus-within{box-shadow:0 0 0 .2rem #7c4dff1a}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:#7c4dff1a}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example)}.md-typeset .example>.admonition-title:after,.md-typeset .example>summary:after{color:#7c4dff}.md-typeset .admonition.quote,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .admonition.quote:focus-within,.md-typeset details.quote:focus-within{box-shadow:0 0 0 .2rem #9e9e9e1a}.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:#9e9e9e1a}.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote)}.md-typeset .quote>.admonition-title:after,.md-typeset .quote>summary:after{color:#9e9e9e}:root{--md-footnotes-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}[dir=ltr] .md-typeset .footnote>ol{margin-left:0}[dir=rtl] .md-typeset .footnote>ol{margin-right:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:focus-within .footnote-backref{opacity:1;transform:translateX(0);transition:none}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-ref{font-size:.75em;font-weight:700}html .md-typeset .footnote-ref{outline-offset:.1rem}.md-typeset [id^="fnref:"]:target>.footnote-ref{outline:auto}.md-typeset .footnote-backref{color:var(--md-typeset-a-color);display:inline-block;font-size:0;opacity:0;transform:translateX(.25rem);transition:color .25s,transform .25s .25s,opacity 125ms .25s;vertical-align:text-bottom}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);opacity:1;transform:translateX(0)}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{background-color:currentcolor;content:"";display:inline-block;height:.8rem;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.8rem}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}[dir=ltr] .md-typeset .headerlink{margin-left:.5rem}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem}.md-typeset .headerlink{color:var(--md-default-fg-color--lighter);display:inline-block;opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{--md-scroll-margin:3.6rem;--md-scroll-offset:0rem;scroll-margin-top:calc(var(--md-scroll-margin) - var(--md-scroll-offset))}@media screen and (min-width:76.25em){.md-header--lifted~.md-container .md-typeset :target{--md-scroll-margin:6rem}}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{--md-scroll-offset:0.2rem}.md-typeset h4:target{--md-scroll-offset:0.15rem}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.984375em){.md-typeset div.arithmatex{margin:0 -.8rem}.md-typeset div.arithmatex>*{width:min-content}}.md-typeset div.arithmatex>*{margin-left:auto!important;margin-right:auto!important;padding:0 .8rem;touch-action:auto}.md-typeset div.arithmatex>* mjx-container{margin:0!important}.md-typeset div.arithmatex mjx-assistive-mml{height:0}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{-webkit-box-decoration-break:clone;box-decoration-break:clone;color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{box-shadow:none;display:block;margin:1em 0;overflow:auto;padding-left:.8rem;padding-right:.8rem}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset details{display:flow-root;overflow:visible;padding-top:0}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){box-shadow:none;padding-bottom:0}.md-typeset details:not([open])>summary{border-radius:.1rem}[dir=ltr] .md-typeset summary{padding-right:1.8rem}[dir=rtl] .md-typeset summary{padding-left:1.8rem}[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset summary{cursor:pointer;display:block;min-height:1rem;overflow:hidden}.md-typeset summary.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset summary:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[dir=ltr] .md-typeset summary:after{right:.4rem}[dir=rtl] .md-typeset summary:after{left:.4rem}.md-typeset summary:after{background-color:currentcolor;content:"";height:1rem;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;transform:rotate(0deg);transition:transform .25s;width:1rem}[dir=rtl] .md-typeset summary:after{transform:rotate(180deg)}.md-typeset summary::marker{display:none}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{--md-icon-size:1.125em;display:inline-flex;height:var(--md-icon-size);vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{fill:currentcolor;max-height:100%;width:var(--md-icon-size)}.md-typeset .lg,.md-typeset .xl,.md-typeset .xxl,.md-typeset .xxxl{vertical-align:text-bottom}.md-typeset .middle{vertical-align:middle}.md-typeset .lg{--md-icon-size:1.5em}.md-typeset .xl{--md-icon-size:2.25em}.md-typeset .xxl{--md-icon-size:3em}.md-typeset .xxxl{--md-icon-size:4em}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{background-color:var(--md-code-hl-color--light);box-shadow:2px 0 0 0 var(--md-code-hl-color) inset;display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em}.highlight span.filename{background-color:var(--md-code-bg-color);border-bottom:.05rem solid var(--md-default-fg-color--lightest);border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flow-root;font-size:.85em;font-weight:700;margin-top:1em;padding:.6617647059em 1.1764705882em;position:relative}.highlight span.filename+pre{margin-top:0}.highlight span.filename+pre>code{border-top-left-radius:0;border-top-right-radius:0}.highlight [data-linenos]:before{background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;color:var(--md-default-fg-color--light);content:attr(data-linenos);float:left;left:-1.1764705882em;margin-left:-1.1764705882em;margin-right:1.1764705882em;padding-left:1.1764705882em;position:sticky;-webkit-user-select:none;user-select:none;z-index:3}.highlight code a[id]{position:absolute;visibility:hidden}.highlight code[data-md-copying]{display:initial}.highlight code[data-md-copying] .hll{display:contents}.highlight code[data-md-copying] .md-annotation{display:none}.highlighttable{display:flow-root}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable th.filename{flex-grow:1;padding:0;text-align:left}.highlighttable th.filename span.filename{margin-top:0}.highlighttable .linenos{background-color:var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-top-left-radius:.1rem;font-size:.85em;padding:.7720588235em 0 .7720588235em 1.1764705882em;-webkit-user-select:none;user-select:none}.highlighttable .linenodiv{box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;padding-right:.5882352941em}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;min-width:0}.linenodiv a{color:inherit}.md-typeset .highlighttable{direction:ltr;margin:1em 0}.md-typeset .highlighttable>tbody>tr>.code>div>pre>code{border-bottom-left-radius:0;border-top-left-radius:0}.md-typeset .highlight+.result{border:.05rem solid var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top-width:.1rem;margin-top:-1.125em;overflow:visible;padding:0 1em}.md-typeset .highlight+.result:after{clear:both;content:"";display:block}@media screen and (max-width:44.984375em){.md-content__inner>.highlight{margin:1em -.8rem}.md-content__inner>.highlight>.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.code>div>pre>code,.md-content__inner>.highlight>.highlighttable>tbody>tr>.filename span.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.linenos,.md-content__inner>.highlight>pre>code{border-radius:0}.md-content__inner>.highlight+.result{border-left-width:0;border-radius:0;border-right-width:0;margin-left:-.8rem;margin-right:-.8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys span{color:var(--md-default-fg-color--light);padding:0 .2em}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{content:"⎇";padding-right:.4em}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{content:"⌘";padding-right:.4em}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{content:"⌃";padding-right:.4em}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{content:"◆";padding-right:.4em}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{content:"⌥";padding-right:.4em}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{content:"⇧";padding-right:.4em}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{content:"❖";padding-right:.4em}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{content:"⊞";padding-right:.4em}.md-typeset .keys .key-arrow-down:before{content:"↓";padding-right:.4em}.md-typeset .keys .key-arrow-left:before{content:"←";padding-right:.4em}.md-typeset .keys .key-arrow-right:before{content:"→";padding-right:.4em}.md-typeset .keys .key-arrow-up:before{content:"↑";padding-right:.4em}.md-typeset .keys .key-backspace:before{content:"⌫";padding-right:.4em}.md-typeset .keys .key-backtab:before{content:"⇤";padding-right:.4em}.md-typeset .keys .key-caps-lock:before{content:"⇪";padding-right:.4em}.md-typeset .keys .key-clear:before{content:"⌧";padding-right:.4em}.md-typeset .keys .key-context-menu:before{content:"☰";padding-right:.4em}.md-typeset .keys .key-delete:before{content:"⌦";padding-right:.4em}.md-typeset .keys .key-eject:before{content:"⏏";padding-right:.4em}.md-typeset .keys .key-end:before{content:"⤓";padding-right:.4em}.md-typeset .keys .key-escape:before{content:"⎋";padding-right:.4em}.md-typeset .keys .key-home:before{content:"⤒";padding-right:.4em}.md-typeset .keys .key-insert:before{content:"⎀";padding-right:.4em}.md-typeset .keys .key-page-down:before{content:"⇟";padding-right:.4em}.md-typeset .keys .key-page-up:before{content:"⇞";padding-right:.4em}.md-typeset .keys .key-print-screen:before{content:"⎙";padding-right:.4em}.md-typeset .keys .key-tab:after{content:"⇥";padding-left:.4em}.md-typeset .keys .key-num-enter:after{content:"⌤";padding-left:.4em}.md-typeset .keys .key-enter:after{content:"⏎";padding-left:.4em}:root{--md-tabbed-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-tabbed-icon--next:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .tabbed-set{border-radius:.1rem;display:flex;flex-flow:column wrap;margin:1em 0;position:relative}.md-typeset .tabbed-set>input{height:0;opacity:0;position:absolute;width:0}.md-typeset .tabbed-set>input:target{--md-scroll-offset:0.625em}.md-typeset .tabbed-set>input.focus-visible~.tabbed-labels:before{background-color:var(--md-accent-fg-color)}.md-typeset .tabbed-labels{-ms-overflow-style:none;box-shadow:0 -.05rem var(--md-default-fg-color--lightest) inset;display:flex;max-width:100%;overflow:auto;scrollbar-width:none}@media print{.md-typeset .tabbed-labels{display:contents}}@media screen{.js .md-typeset .tabbed-labels{position:relative}.js .md-typeset .tabbed-labels:before{background:var(--md-default-fg-color);bottom:0;content:"";display:block;height:2px;left:0;position:absolute;transform:translateX(var(--md-indicator-x));transition:width 225ms,background-color .25s,transform .25s;transition-timing-function:cubic-bezier(.4,0,.2,1);width:var(--md-indicator-width)}}.md-typeset .tabbed-labels::-webkit-scrollbar{display:none}.md-typeset .tabbed-labels>label{border-bottom:.1rem solid #0000;border-radius:.1rem .1rem 0 0;color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;font-size:.64rem;font-weight:700;padding:.78125em 1.25em .625em;scroll-margin-inline-start:1rem;transition:background-color .25s,color .25s;white-space:nowrap;width:auto}@media print{.md-typeset .tabbed-labels>label:first-child{order:1}.md-typeset .tabbed-labels>label:nth-child(2){order:2}.md-typeset .tabbed-labels>label:nth-child(3){order:3}.md-typeset .tabbed-labels>label:nth-child(4){order:4}.md-typeset .tabbed-labels>label:nth-child(5){order:5}.md-typeset .tabbed-labels>label:nth-child(6){order:6}.md-typeset .tabbed-labels>label:nth-child(7){order:7}.md-typeset .tabbed-labels>label:nth-child(8){order:8}.md-typeset .tabbed-labels>label:nth-child(9){order:9}.md-typeset .tabbed-labels>label:nth-child(10){order:10}.md-typeset .tabbed-labels>label:nth-child(11){order:11}.md-typeset .tabbed-labels>label:nth-child(12){order:12}.md-typeset .tabbed-labels>label:nth-child(13){order:13}.md-typeset .tabbed-labels>label:nth-child(14){order:14}.md-typeset .tabbed-labels>label:nth-child(15){order:15}.md-typeset .tabbed-labels>label:nth-child(16){order:16}.md-typeset .tabbed-labels>label:nth-child(17){order:17}.md-typeset .tabbed-labels>label:nth-child(18){order:18}.md-typeset .tabbed-labels>label:nth-child(19){order:19}.md-typeset .tabbed-labels>label:nth-child(20){order:20}}.md-typeset .tabbed-labels>label:hover{color:var(--md-default-fg-color)}.md-typeset .tabbed-labels>label>[href]:first-child{color:inherit}.md-typeset .tabbed-labels--linked>label{padding:0}.md-typeset .tabbed-labels--linked>label>a{display:block;padding:.78125em 1.25em .625em}.md-typeset .tabbed-content{width:100%}@media print{.md-typeset .tabbed-content{display:contents}}.md-typeset .tabbed-block{display:none}@media print{.md-typeset .tabbed-block{display:block}.md-typeset .tabbed-block:first-child{order:1}.md-typeset .tabbed-block:nth-child(2){order:2}.md-typeset .tabbed-block:nth-child(3){order:3}.md-typeset .tabbed-block:nth-child(4){order:4}.md-typeset .tabbed-block:nth-child(5){order:5}.md-typeset .tabbed-block:nth-child(6){order:6}.md-typeset .tabbed-block:nth-child(7){order:7}.md-typeset .tabbed-block:nth-child(8){order:8}.md-typeset .tabbed-block:nth-child(9){order:9}.md-typeset .tabbed-block:nth-child(10){order:10}.md-typeset .tabbed-block:nth-child(11){order:11}.md-typeset .tabbed-block:nth-child(12){order:12}.md-typeset .tabbed-block:nth-child(13){order:13}.md-typeset .tabbed-block:nth-child(14){order:14}.md-typeset .tabbed-block:nth-child(15){order:15}.md-typeset .tabbed-block:nth-child(16){order:16}.md-typeset .tabbed-block:nth-child(17){order:17}.md-typeset .tabbed-block:nth-child(18){order:18}.md-typeset .tabbed-block:nth-child(19){order:19}.md-typeset .tabbed-block:nth-child(20){order:20}}.md-typeset .tabbed-block>.highlight:first-child>pre,.md-typeset .tabbed-block>pre:first-child{margin:0}.md-typeset .tabbed-block>.highlight:first-child>pre>code,.md-typeset .tabbed-block>pre:first-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child>.filename{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable{margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.filename span.filename,.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.linenos{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.code>div>pre>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child+.result{margin-top:-.125em}.md-typeset .tabbed-block>.tabbed-set{margin:0}.md-typeset .tabbed-button{align-self:center;border-radius:100%;color:var(--md-default-fg-color--light);cursor:pointer;display:block;height:.9rem;margin-top:.1rem;pointer-events:auto;transition:background-color .25s;width:.9rem}.md-typeset .tabbed-button:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .tabbed-button:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-tabbed-icon--prev);mask-image:var(--md-tabbed-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color .25s,transform .25s;width:100%}.md-typeset .tabbed-control{background:linear-gradient(to right,var(--md-default-bg-color) 60%,#0000);display:flex;height:1.9rem;justify-content:start;pointer-events:none;position:absolute;transition:opacity 125ms;width:1.2rem}[dir=rtl] .md-typeset .tabbed-control{transform:rotate(180deg)}.md-typeset .tabbed-control[hidden]{opacity:0}.md-typeset .tabbed-control--next{background:linear-gradient(to left,var(--md-default-bg-color) 60%,#0000);justify-content:end;right:0}.md-typeset .tabbed-control--next .tabbed-button:after{-webkit-mask-image:var(--md-tabbed-icon--next);mask-image:var(--md-tabbed-icon--next)}@media screen and (max-width:44.984375em){[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels{margin:0 -.8rem;max-width:100vw;scroll-padding-inline-start:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels:after{content:""}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-right:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-left:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-right:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{width:2rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-left:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-right:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-left:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{width:2rem}}@media screen{.md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){color:var(--md-default-fg-color)}.md-typeset .no-js .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .no-js .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .no-js .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .no-js .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .no-js .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .no-js .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .no-js .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .no-js .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .no-js .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .no-js .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .no-js .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .no-js .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .no-js .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .no-js .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .no-js .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .no-js .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .no-js .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .no-js .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .no-js .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .no-js .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.no-js .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.no-js .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.no-js .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.no-js .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.no-js .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.no-js .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.no-js .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.no-js .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.no-js .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.no-js .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.no-js .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.no-js .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.no-js .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.no-js .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.no-js .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.no-js .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.no-js .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.no-js .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.no-js .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.no-js .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){border-color:var(--md-default-fg-color)}}.md-typeset .tabbed-set>input:first-child.focus-visible~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10).focus-visible~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11).focus-visible~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12).focus-visible~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13).focus-visible~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14).focus-visible~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15).focus-visible~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16).focus-visible~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17).focus-visible~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18).focus-visible~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19).focus-visible~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2).focus-visible~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20).focus-visible~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3).focus-visible~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4).focus-visible~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5).focus-visible~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6).focus-visible~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7).focus-visible~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8).focus-visible~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9).focus-visible~.tabbed-labels>:nth-child(9){color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:first-child:checked~.tabbed-content>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-content>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-content>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-content>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-content>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-content>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-content>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-content>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-content>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-content>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-content>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-content>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-content>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-content>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-content>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-content>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-content>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-content>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-content>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-content>:nth-child(9){display:block}:root{--md-tasklist-icon:url('data:image/svg+xml;charset=utf-8,');--md-tasklist-icon--checked:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .task-list-item{list-style-type:none;position:relative}[dir=ltr] .md-typeset .task-list-item [type=checkbox]{left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}[dir=ltr] .md-typeset .task-list-indicator:before{left:-1.5em}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em}.md-typeset .task-list-indicator:before{background-color:var(--md-default-fg-color--lightest);content:"";height:1.25em;-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.15em;width:1.25em}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}:root>*{--md-mermaid-font-family:var(--md-text-font-family),sans-serif;--md-mermaid-edge-color:var(--md-code-fg-color);--md-mermaid-node-bg-color:var(--md-accent-fg-color--transparent);--md-mermaid-node-fg-color:var(--md-accent-fg-color);--md-mermaid-label-bg-color:var(--md-default-bg-color);--md-mermaid-label-fg-color:var(--md-code-fg-color);--md-mermaid-sequence-actor-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actor-fg-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-actor-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-actor-line-color:var(--md-default-fg-color--lighter);--md-mermaid-sequence-actorman-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actorman-line-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-box-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-box-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-label-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-label-fg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-loop-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-loop-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-loop-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-message-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-message-line-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-note-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-border-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-number-bg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-number-fg-color:var(--md-accent-bg-color)}.mermaid{line-height:normal;margin:1em 0}.md-typeset .grid{grid-gap:.4rem;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));margin:1em 0}.md-typeset .grid.cards>ol,.md-typeset .grid.cards>ul{display:contents}.md-typeset .grid.cards>ol>li,.md-typeset .grid.cards>ul>li,.md-typeset .grid>.card{border:.05rem solid var(--md-default-fg-color--lightest);border-radius:.1rem;display:block;margin:0;padding:.8rem;transition:border .25s,box-shadow .25s}.md-typeset .grid.cards>ol>li:focus-within,.md-typeset .grid.cards>ol>li:hover,.md-typeset .grid.cards>ul>li:focus-within,.md-typeset .grid.cards>ul>li:hover,.md-typeset .grid>.card:focus-within,.md-typeset .grid>.card:hover{border-color:#0000;box-shadow:var(--md-shadow-z2)}.md-typeset .grid.cards>ol>li>hr,.md-typeset .grid.cards>ul>li>hr,.md-typeset .grid>.card>hr{margin-bottom:1em;margin-top:1em}.md-typeset .grid.cards>ol>li>:first-child,.md-typeset .grid.cards>ul>li>:first-child,.md-typeset .grid>.card>:first-child{margin-top:0}.md-typeset .grid.cards>ol>li>:last-child,.md-typeset .grid.cards>ul>li>:last-child,.md-typeset .grid>.card>:last-child{margin-bottom:0}.md-typeset .grid>*,.md-typeset .grid>.admonition,.md-typeset .grid>.highlight>*,.md-typeset .grid>.highlighttable,.md-typeset .grid>.md-typeset details,.md-typeset .grid>details,.md-typeset .grid>pre{margin-bottom:0;margin-top:0}.md-typeset .grid>.highlight>pre:only-child,.md-typeset .grid>.highlight>pre>code,.md-typeset .grid>.highlighttable,.md-typeset .grid>.highlighttable>tbody,.md-typeset .grid>.highlighttable>tbody>tr,.md-typeset .grid>.highlighttable>tbody>tr>.code,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre>code{height:100%}.md-typeset .grid>.tabbed-set{margin-bottom:0;margin-top:0}@media screen and (min-width:45em){[dir=ltr] .md-typeset .inline{float:left}[dir=rtl] .md-typeset .inline{float:right}[dir=ltr] .md-typeset .inline{margin-right:.8rem}[dir=rtl] .md-typeset .inline{margin-left:.8rem}.md-typeset .inline{margin-bottom:.8rem;margin-top:0;width:11.7rem}[dir=ltr] .md-typeset .inline.end{float:right}[dir=rtl] .md-typeset .inline.end{float:left}[dir=ltr] .md-typeset .inline.end{margin-left:.8rem;margin-right:0}[dir=rtl] .md-typeset .inline.end{margin-left:0;margin-right:.8rem}}
\ No newline at end of file
diff --git a/assets/stylesheets/main.35f28582.min.css.map b/assets/stylesheets/main.35f28582.min.css.map
deleted file mode 100644
index 81904e5..0000000
--- a/assets/stylesheets/main.35f28582.min.css.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["src/templates/assets/stylesheets/main/components/_meta.scss","../../../../src/templates/assets/stylesheets/main.scss","src/templates/assets/stylesheets/main/_resets.scss","src/templates/assets/stylesheets/main/_colors.scss","src/templates/assets/stylesheets/main/_icons.scss","src/templates/assets/stylesheets/main/_typeset.scss","src/templates/assets/stylesheets/utilities/_break.scss","src/templates/assets/stylesheets/main/components/_author.scss","src/templates/assets/stylesheets/main/components/_banner.scss","src/templates/assets/stylesheets/main/components/_base.scss","src/templates/assets/stylesheets/main/components/_clipboard.scss","src/templates/assets/stylesheets/main/components/_code.scss","src/templates/assets/stylesheets/main/components/_consent.scss","src/templates/assets/stylesheets/main/components/_content.scss","src/templates/assets/stylesheets/main/components/_dialog.scss","src/templates/assets/stylesheets/main/components/_feedback.scss","src/templates/assets/stylesheets/main/components/_footer.scss","src/templates/assets/stylesheets/main/components/_form.scss","src/templates/assets/stylesheets/main/components/_header.scss","node_modules/material-design-color/material-color.scss","src/templates/assets/stylesheets/main/components/_nav.scss","src/templates/assets/stylesheets/main/components/_pagination.scss","src/templates/assets/stylesheets/main/components/_post.scss","src/templates/assets/stylesheets/main/components/_progress.scss","src/templates/assets/stylesheets/main/components/_search.scss","src/templates/assets/stylesheets/main/components/_select.scss","src/templates/assets/stylesheets/main/components/_sidebar.scss","src/templates/assets/stylesheets/main/components/_source.scss","src/templates/assets/stylesheets/main/components/_status.scss","src/templates/assets/stylesheets/main/components/_tabs.scss","src/templates/assets/stylesheets/main/components/_tag.scss","src/templates/assets/stylesheets/main/components/_tooltip.scss","src/templates/assets/stylesheets/main/components/_tooltip2.scss","src/templates/assets/stylesheets/main/components/_top.scss","src/templates/assets/stylesheets/main/components/_version.scss","src/templates/assets/stylesheets/main/extensions/markdown/_admonition.scss","src/templates/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/templates/assets/stylesheets/main/extensions/markdown/_toc.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/templates/assets/stylesheets/main/integrations/_mermaid.scss","src/templates/assets/stylesheets/main/modifiers/_grid.scss","src/templates/assets/stylesheets/main/modifiers/_inline.scss"],"names":[],"mappings":"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CJ,CmB9qCI,8CAEE,kCAAA,CAAA,0BnB+qCN,CACF,CKj3CI,0Cc0MA,0CACE,YnB0qCJ,CmBvqCI,yDACE,UnByqCN,CmBrqCI,wDACE,YnBuqCN,CmBnqCI,kDACE,YnBqqCN,CmBhqCE,gBAIE,iDAAA,CADA,gCAAA,CAFA,aAAA,CACA,enBoqCJ,CACF,CK96CM,+DcmRF,6CACE,YnB8pCJ,CmB3pCI,4DACE,UnB6pCN,CmBzpCI,2DACE,YnB2pCN,CmBvpCI,qDACE,YnBypCN,CACF,CKt6CI,mCc7JJ,QAgbI,oBnBupCF,CmBjpCI,kCAME,qCAAA,CACA,qDAAA,CANA,eAAA,CACA,KAAA,CAGA,SnBmpCN,CmB9oCM,6CACE,uBnBgpCR,CmB5oCM,gDACE,YnB8oCR,CmBzoCI,2CACE,kBnB4oCN,CmB7oCI,2CACE,mBnB4oCN,CmB7oCI,iCAEE,oBnB2oCN,CmBpoCI,yDACE,kBnBsoCN,CmBvoCI,yDACE,iBnBsoCN,CACF,CK/7CI,sCc7JJ,QA4dI,oBAAA,CACA,oDnBooCF,CmB9nCI,gCAME,qCAAA,CACA,qDAAA,CANA,eAAA,CACA,KAAA,CAGA,SnBgoCN,CmB3nCM,8CACE,uBnB6nCR,CmBznCM,8CACE,YnB2nCR,CmBtnCI,yCACE,kBnBynCN,CmB1nCI,yCACE,mBnBynCN,CmB1nCI,+BAEE,oBnBwnCN,CmBjnCI,uDACE,kBnBmnCN,CmBpnCI,uDACE,iBnBmnCN,CmB9mCE,wBACE,YAAA,CACA,sBAAA,CAEA,SAAA,CACA,6FACE,CAHF,mBnBknCJ,CmB1mCI,sCACE,enB4mCN,CmBvmCE,iFACE,sBAAA,CAEA,SAAA,CACA,4FACE,CAHF,kBnB2mCJ,CmBlmCE,iDACE,enBomCJ,CmBhmCE,6CACE,YnBkmCJ,CmB9lCE,uBACE,aAAA,CACA,enBgmCJ,CmB7lCI,kCACE,enB+lCN,CmB3lCI,qCACE,enB6lCN,CmB1lCM,0CACE,uCnB4lCR,CmBxlCM,6DACE,mBnB0lCR,CmBtlCM,yFAEE,YnBwlCR,CmBnlCI,yCAEE,kBnBulCN,CmBzlCI,yCAEE,mBnBulCN,CmBzlCI,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","file":"main.css"}
\ No newline at end of file
diff --git a/index.html b/index.html
index 7f41427..f20d0f8 100644
--- a/index.html
+++ b/index.html
@@ -16,7 +16,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
@@ -1702,7 +1702,7 @@ Community
-
+
diff --git a/search/search_index.json b/search/search_index.json
index 3654a5c..bdb1965 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Arcalot","text":"The Arcalot community is dedicated to developing modular tools, plugins, and libraries with flexible implementations to be used independently or as complete end-to-end solutions. We believe in enabling automation and portability of complex tasks and in pre-validating actions to avoid costly re-runs due to late failures and incompatible data.
"},{"location":"#arcaflow","title":"Arcaflow","text":"Arcaflow is a workflow orchestration system consisting of three main components:
- The Arcaflow engine - Written in Go and delivered as a single binary
- Plugins - Delivered as Linux containers and developed with SDKs
- Workflow definitions - Written in YAML to sequence plugins and direct data
Arcaflow is highly-flexible and portable, helping you to build pipelines of actions via plugins. Plugin steps typically perform one action well, creating or manipulating data that is returned in a machine-readable format. Data is validated according to schemas as it passes through the pipeline in order to clearly diagnose type mismatch problems early. Arcaflow runs on your laptop, a jump host, or in a CI system, requiring only the Arcaflow engine binary, a workflow definition in YAML, and a compatible container runtime.
Arcaflow allows you to encapsulate and version-control expertise, making potentially very complex workflows easily portable among environments and automation systems. With an Arcaflow workflow, you can carefully craft a pipeline of actions that serves your direct needs and share that workflow virtually unchanged for others to run in different environments and CI/CD systems.
An ever-growing catalog of official plugins are maintained within the Arcalot organization and are available as versioned containers from Quay.io. You can also build your own containerized plugins using the the Arcaflow SDK, available for Python and Golang. We encourage you to contribute your plugins to the community, and you can start by adding them to the plugins incubator repo via a pull request.
"},{"location":"#documentation","title":"Documentation","text":"We work hard to bring the documentation to the user, meaning that you should find a lot of relevant documentation in the context of what you may be working on via readme files, be it the engine, the SDK, a plugin, a workflow, or a sub-component. Comprehensive documentation, developer references, and quickstart guides will always be located in the arcalot.io pages.
"},{"location":"#community","title":"Community","text":"We invite you to contribute! Check out the Issues in the individual repositories for ideas on where to get involved, or consider contributing a new plugin by starting with our python plugin template repository. Outside contributions and pull requests are of course always welcome.
If you want to get more involved with contributions, maintenance, and governance, consider joining the Arcalot Round Table (ART), our central community body. The ART currently holds bi-weekly video conference meetings. Please reach out to one of our ART chairs for more information.
You can find our general community health files like our code of conduct and contribution guidelines in the .github repository. If you have any questions or suggestions, please use the Issues in the respective repository.
"},{"location":"arcaflow/","title":"Arcaflow: The noble workflow engine","text":"Arcaflow is a workflow engine that lets you run individual steps and pass data between them. The data is validated according to a schema along the way to make sure there is no corrupt data. Arcaflow runs on your laptop, a jump host, or in a CI system and deploys plugins as containers on target systems via Docker, Podman, or Kubernetes.
Did you know?
In Docker/Kubernetes, Arcaflow only needs network access to the API, not the plugin container itself. You can safely place a restrictive firewall on most plugins.
Use cases Arcaflow is a good fit to:
- Run ad-hoc tasks across container systems
- Pass data between them
- Make sure your data is correct
- Make workflows portable with minimal dependencies
You can use Arcaflow for many things. We use it for:
- Performance and chaos testing
- Ad-hoc workflows without previous deployment
- Vendor-independent CI workflows
Get started \u00bb Contribute \u00bb
Shipping expertise Good workflows take time and expertise to develop. Often these workflows evolve organically into bespoke scripts and/or application stacks, and knowledge transfer or the ability to run the workflows in new environments can be very difficult. Arcaflow addresses this problem by focusing on being the plumbing for the workflow, standardizing on a plugin architecture for all actions, minimizing dependencies, focusing on quality, and enforcing strong typing for data passing.
Arcaflow\u2019s design can drastically simplify much of the workflow creation process, and it allows the workflow author to ensure the workflow is locked in end-to-end. A complete workflow can be version-controlled as a simple YAML file and in most cases can be expected to run in exactly the same way in any compatible environment.
Not a CI system Arcaflow is not designed to run as a persistent service nor to record workflow histories, and in most cases it is probably not the best tool to set up or manage infrastructure. For end-to-end CI needs, you should leverage a system that provides these and other features (possibly something from the Alternatives list below).
Arcaflow is, however, an excellent companion to a CI system. In many cases, building complex workflows completely within a CI environment can effectively lock you into that system because the workflow may not be easily portable outside of it or run independently by a user. An Arcaflow workflow can be easily integrated into most CI systems, so a workflow that you define once may be moved in most cases without modification to different environments or run directly by users.
Alternatives It\u2019s important that you pick the right tool for the job. Sometimes, you need something simple. Sometimes, you want something persistent that keeps track of the workflows you run over time. We have collected some common and well-known open source workflow and workflow-like engines into this list and have provided some comparisons to help you find the right tool for your needs.
Here are some of the features that make Arcaflow a unique solution to the below alternatives:
- Designed for complex branching-action workflows and parallelization
- Prioritizes data passing and management via strong typing and schemas to ensure machine readability, workflow validation, and data integrity
- Runs actions as plugins via container orchestrator APIs
- Engine is deployed as a single Golang binary, and plugins are run as containers, minimizing dependencies and maximizing portability
- Workflows are designed to be explicitly version controlled to ensure portability to other environments without code or feature drift
- Plugins can be written in a variety of languages, and plugins from different languages can be mixed into the same workflow (SDKs provided currently for Python and Golang)
Ansible Ansible is an IT automation and configuration management system. It handles configuration management, application deployment, cloud provisioning, ad-hoc task execution, network automation, and multi-node orchestration. Ansible makes complex changes like zero-downtime rolling updates with load balancers easy.
How are Arcaflow and Ansible similar?
- They both perform actions on local and remote systems using modular architectures.
- Their core engines can both run on your laptop and don\u2019t need a large server.
- You don\u2019t need to deploy them on target hosts or run them permanently.
- They both allow for passing of data between steps.
- They both use YAML to define their workflows.
- Their plugins and modules can be written in a variety of languages.
How is Ansible different?
- Ansible is well-established with a wide range of available plugins.
- Ansible runs its tasks typically as commands over remote shells.
- Ansible\u2019s approach to parallelization is in terms of executing the same tasks against different hosts in parallel (see \u201cforks\u201d and \u201cstrategy\u201d). Defining different tasks to perform in parallel is more challenging.
- Ansible is written in Python and has many dependencies on the control host, though it can be run as a container to simplify this.
- Some modules may have system requirements for python or other dependencies on the target hosts/containers. (See Ansible documentation)
- Ansible workflows may not be consistent or portable across bare metal and Kubernetes environments.
Apache Airflow Airflow is a platform to programmatically author, schedule, and monitor workflows. It is a deployed workflow engine written in Python.
How are Arcaflow and Airflow similar?
- They both run workflows that allow you to pass data between individual steps.
- They are both good a parallelizing tasks.
- They both allow you to write workflow steps in Python.
How is Airflow different?
- Airflow must be deployed and run continuously.
- Airflow needs a persistence engine (database) to store data long-term.
- Airflow workflows and operators can only be written in Python.
- Operator code is tightly coupled to Airflow.
- Operators have to explicitly consider container engines if you want to run things in a container.
Argo Workflows Argo Workflows is a container-native workflow engine for orchestrating parallel jobs on Kubernetes. Argo Workflows is implemented as a Kubernetes CRD (Custom Resource Definition).
How are Arcaflow and Argo Workflows similar?
- They can both run several steps.
- They can both run containers as workflow steps.
- They both allow for passing of data between steps.
How is Argo Workflows different?
- Argo Workflows allows you to define workflows directly in Kubernetes custom resources.
- Argo Workflows allows you to run any container image as a step.
- Argo Workflows only runs as a Kubernetes operator and cannot run outside of it.
- Argo Workflows only supports a single Kubernetes cluster.
- In order to run things in parallel, you need to hand-write a DAG
Netflix Conductor Conductor is a platform created by Netflix to orchestrate workflows that span across microservices.
How are Arcaflow and Conductor similar?
- They both allow for passing of data between steps.
- They are both good a parallelizing tasks.
How is Conductor different?
- Conductor has a user interface.
- Conductor keeps a history of jobs that have run in the past.
- Conductor must be deployed and run continuously.
- Conductor needs a persistence engine (database) to store data long-term.
- Conductor requires at least 16 GB of RAM to run everything.
- Workers must reach Conductor over HTTP and must be explicitly deployed.
Tekton Tekton is a framework for creating CI/CD systems, allowing developers to build, test, and deploy across cloud providers and on-premise systems.
How are Arcaflow and Tekton similar?
- They both have tasks and pipelines to run steps in sequence.
- They both allow for passing of data between steps.
How is Tekton different?
- Tekton is a full CI/CD system that can be coupled to your version control system without additional tools.
- Tekton has built-in supply chain security.
- Tekton allows you to run any container image without additional integrations.
- Tekton is deployed inside a Kubernetes cluster and cannot exist without it.
- Tekton has no officially-supported integrations with other platforms and tools.
qDup qDup allows shell commands to be queued up across multiple servers to coordinate performance tests. It is designed to follow the same workflow as a user at a terminal so that commands can be performed with or without qDup. Commands are grouped into re-usable scripts that are mapped to different hosts by roles.
How are Arcaflow and qDup similar?
- They are both workflow systems.
- They both run on your laptop and don\u2019t need a large server.
- You don\u2019t need to deploy them on target hosts or run them permanently.
- They can both run things on remote systems out of the box.
How is qDup different?
- qDup has advanced controls for creating loops, signaling, waiting for events, etc.
- qDup makes it very simple to run scripts in a parallelized way.
- qDup runs commands over SSH locally, or integrates with podman. There are plans to support Docker and Kubernetes in the future.
- qDup is written in Java.
"},{"location":"arcaflow/getting-started/","title":"Arcaflow Getting Started Guide","text":""},{"location":"arcaflow/getting-started/#running-workflows","title":"Running Workflows","text":"An Arcaflow workflow is a definition of steps structured together to perform complex actions. Workflows are defined as machine-readable YAML and therefore can be version-controlled and shared easily to run in different environments. A workflow is a way of encapsulating and sharing expertise and ensuring reproducible results.
The requirements for running a workflow are simple. You just need the Arcaflow engine binary, a workflow definition file, and, typically, an input file. You can also provide a config file, which allows for setting workflow defaults, such as log levels. The contents of the input and configuration files, like the workflow file, are YAML. Finally, you need an appropriate container platform, such as Podman, Docker, or Kubernetes, as the target of the workflow execution.
Note
The default container platform for the Arcaflow engine is Podman. To use another platform, a configuration file is required.
A repository of example workflows is available for reference and practice. Let\u2019s try running the basic example.
First we will clone the example workflows repository:
git clone https://github.com/arcalot/arcaflow-workflows.git\n
Then we will run the workflow, setting the workflow directory as the context, and defining the workflow, configuration, and input files to use:
arcaflow --context arcaflow-workflows/basic-examples/basic/ \\\n--workflow workflow.yaml --config config.yaml --input input.yaml\n
Arcaflow will display logs, the detail of which determined by the configuration file, and then will return the machine-readable output of the workflow in YAML format:
basic workflow output YAMLoutput_data:\n example:\n message: Hello, Arcalot!\noutput_id: success\n
It\u2019s that simple! And the basics of running a workflow are the same, whether it\u2019s this single-step hello-world example:
flowchart LR\n%% Success path\nsteps.example.deploy-->steps.example.starting\nsteps.example.running-->steps.example.outputs\nsteps.example.starting-->steps.example.running\nsteps.example.starting-->steps.example.starting.started\nsteps.example.disabled-->steps.example.disabled.output\nsteps.example.outputs-->steps.example.outputs.success\nsteps.example.enabling-->steps.example.enabling.resolved\nsteps.example.enabling-->steps.example.starting\nsteps.example.enabling-->steps.example.disabled\nsteps.example.outputs.success-->outputs.success\ninput-->steps.example.starting\nsteps.example.cancelled-->steps.example.outputs
\u2026 or a much more complex workflow like this stress-ng plus PCP data collection example:
%% Mermaid markdown workflow\nflowchart LR\n%% Success path\nsteps.pcp.enabling-->steps.pcp.disabled\nsteps.pcp.enabling-->steps.pcp.enabling.resolved\nsteps.pcp.enabling-->steps.pcp.starting\nsteps.stressng.disabled-->steps.stressng.disabled.output\nsteps.stressng.cancelled-->steps.stressng.outputs\nsteps.pre_wait.cancelled-->steps.pre_wait.outputs\nsteps.pcp.outputs.success-->outputs.success\nsteps.pcp.disabled-->steps.pcp.disabled.output\nsteps.uuidgen.outputs.success-->outputs.success\nsteps.uuidgen.outputs-->steps.uuidgen.outputs.success\nsteps.pre_wait.running-->steps.pre_wait.outputs\nsteps.pcp.starting-->steps.pcp.starting.started\nsteps.pcp.starting-->steps.pcp.running\nsteps.pcp.starting.started-->steps.pre_wait.starting\nsteps.pcp.running-->steps.pcp.outputs\nsteps.uuidgen.starting-->steps.uuidgen.starting.started\nsteps.uuidgen.starting-->steps.uuidgen.running\nsteps.pre_wait.disabled-->steps.pre_wait.disabled.output\nsteps.stressng.deploy-->steps.stressng.starting\nsteps.pcp.outputs-->steps.pcp.outputs.success\nsteps.stressng.outputs-->steps.stressng.outputs.success\nsteps.stressng.outputs-->steps.post_wait.starting\nsteps.post_wait.cancelled-->steps.post_wait.outputs\nsteps.stressng.outputs.success-->outputs.success\nsteps.pre_wait.enabling-->steps.pre_wait.enabling.resolved\nsteps.pre_wait.enabling-->steps.pre_wait.starting\nsteps.pre_wait.enabling-->steps.pre_wait.disabled\nsteps.post_wait.outputs-->steps.post_wait.outputs.success\nsteps.post_wait.outputs-->steps.pcp.cancelled\nsteps.uuidgen.disabled-->steps.uuidgen.disabled.output\nsteps.uuidgen.cancelled-->steps.uuidgen.outputs\nsteps.pcp.cancelled-->steps.pcp.outputs\nsteps.stressng.enabling-->steps.stressng.starting\nsteps.stressng.enabling-->steps.stressng.disabled\nsteps.stressng.enabling-->steps.stressng.enabling.resolved\nsteps.stressng.running-->steps.stressng.outputs\nsteps.post_wait.starting-->steps.post_wait.starting.started\nsteps.post_wait.starting-->steps.post_wait.running\nsteps.uuidgen.deploy-->steps.uuidgen.starting\nsteps.uuidgen.running-->steps.uuidgen.outputs\nsteps.post_wait.deploy-->steps.post_wait.starting\nsteps.stressng.starting-->steps.stressng.starting.started\nsteps.stressng.starting-->steps.stressng.running\nsteps.pcp.deploy-->steps.pcp.starting\nsteps.post_wait.disabled-->steps.post_wait.disabled.output\nsteps.post_wait.running-->steps.post_wait.outputs\nsteps.pre_wait.outputs-->steps.pre_wait.outputs.success\nsteps.pre_wait.outputs-->steps.stressng.starting\nsteps.uuidgen.enabling-->steps.uuidgen.enabling.resolved\nsteps.uuidgen.enabling-->steps.uuidgen.starting\nsteps.uuidgen.enabling-->steps.uuidgen.disabled\nsteps.pre_wait.starting-->steps.pre_wait.starting.started\nsteps.pre_wait.starting-->steps.pre_wait.running\nsteps.post_wait.enabling-->steps.post_wait.enabling.resolved\nsteps.post_wait.enabling-->steps.post_wait.starting\nsteps.post_wait.enabling-->steps.post_wait.disabled\nsteps.pre_wait.deploy-->steps.pre_wait.starting\ninput-->steps.stressng.starting\ninput-->steps.pcp.starting
Learn more about running workflows \u00bb
"},{"location":"arcaflow/getting-started/#writing-workflows","title":"Writing Workflows","text":"As a workflow author, you determine the steps of the workflow, how data will pass between the steps, what input is required from the workflow user, and what output will be returned. It is possible to build very complex workflows with data translations, sub-workflows, parallelisim and serialization, and multiple output paths.
Let\u2019s start with something simple. Our workflow will collect a nickname
input from the user and will pass that input to an example \u201cHello world!\u201d step. The workflow will also run a UUID generation step in parallel to the example step, and it will return both the UUID and a greeting.
In the first part of the workflow.yaml
file we will define the workflow compatibility version and the input schema for the workflow. In the input schema, we are expecting only a single input called nickname
with a type of string
.
workflow.yaml (excerpt)version: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n nickname: #<<== Input key name\n display:\n description: Just a name\n name: Name\n required: true\n type:\n type_id: string #<<== Input value type\n...\n
Next we will define the steps of the workflow. The steps are to be deployed as container images, where the src
field defines the image and tag. The arcaflow-plugin-utilities
plugin has multiple steps available, so we indicate with the step: uuid
field which step we want to run. The arcaflow-plugin-example
plugin has only one step, so the step
field is not required. The uuidgen
step requires no input, so we pass an empty object {}
to it. The example
plugin requires an input object of name
with _type
and nick
fields. We statically set the value of the _type
field in the input object of the step, and then we use the Arcaflow expression language to reference the workflow input value for nickname
as the input to the plugin\u2019s nick
field.
workflow.yaml (excerpt)...\nsteps:\n uuidgen: #<<== Step name\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-utilities:0.6.0 #<<== Container image\n step: uuid #<<== Specific plugin step\n input: {} #<<== Step does not require input\n example: #<<== Step name\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-example:0.5.0 #<<== Container image\n input:\n name:\n _type: nickname #<<== Statically-defined input\n nick: !expr $.input.nickname #<<== Referenced workflow input\n...\n
Finally we define the outputs that we expect when the workflow succeeds. The workflow will run until all of the items referenced in the success
sub-object become available or until a step that one of the items depends on fails. Once all of the items referenced in the success
sub-object become available, the engine will terminate any steps which have not yet completed, and it will return the success
sub-object. If a required step fails, then the workflow will fail.
Tip
It is possible to define multiple sub-objects for outputs
with different dependencies. In this case, the output sub-oject that has its dependencies satisfied first will be the one that returns and ends the workflow. See the documentation for more info.
workflow.yaml (excerpt)...\noutputs:\n success:\n uuid: !expr $.steps.uuidgen.outputs.success\n example: !expr $.steps.example.outputs.success\n
Our final workflow looks like this:
workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n nickname:\n display:\n description: Just a name\n name: Name\n required: true\n type:\n type_id: string\n\nsteps:\n uuidgen:\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-utilities:0.6.0\n step: uuid\n input: {}\n example:\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-example:0.5.0\n input:\n name:\n _type: nickname\n nick: !expr $.input.nickname\n\noutputs:\n success:\n uuid: !expr $.steps.uuidgen.outputs.success.uuid\n example: !expr $.steps.example.outputs.success.message\n
We will create an input file to satisfy the input schema of the workflow:
input.yamlnickname: Arcalot\n
We will also create a configuration file, setting the container deployer to Podman and the log levels to error
:
config.yamllog:\n level: error\nlogged_outputs:\n error:\n level: error\ndeployers:\n image:\n deployer_name: podman\n
And now we can run our new workflow:
Tip
The default workflow file is workflow.yaml
so we don\u2019t need to specifiy it here explicitly.
arcaflow --config config.yaml --input input.yaml\n
example workflow output YAMLoutput_data:\n example: Hello, Arcalot!\n uuid: b98909c2-4a25-4cc1-8222-3290b0621129\noutput_id: success\n
Learn more about workflow concepts \u00bb
Learn more about writing workflows \u00bb
See more example workflows \u00bb
Did you know?
Arcaflow provides Mermaid markdown in the workflow debug output that allows you to quickly visualize the workflow in a graphic format. You can grab the Mermaid graph you see in the output and put it into a Mermaid editor.
Mermaid markdownMermaid rendered flowchart flowchart LR\nsteps.uuidgen.enabling-->steps.uuidgen.starting\nsteps.uuidgen.enabling-->steps.uuidgen.disabled\nsteps.uuidgen.enabling-->steps.uuidgen.enabling.resolved\nsteps.uuidgen.outputs-->steps.uuidgen.outputs.success\nsteps.example.cancelled-->steps.example.outputs\nsteps.example.outputs.success-->outputs.success\ninput-->steps.example.starting\nsteps.uuidgen.disabled-->steps.uuidgen.disabled.output\nsteps.uuidgen.outputs.success-->outputs.success\nsteps.example.disabled-->steps.example.disabled.output\nsteps.example.running-->steps.example.outputs\nsteps.example.enabling-->steps.example.enabling.resolved\nsteps.example.enabling-->steps.example.starting\nsteps.example.enabling-->steps.example.disabled\nsteps.example.starting-->steps.example.starting.started\nsteps.example.starting-->steps.example.running\nsteps.example.deploy-->steps.example.starting\nsteps.uuidgen.deploy-->steps.uuidgen.starting\nsteps.example.outputs-->steps.example.outputs.success\nsteps.uuidgen.cancelled-->steps.uuidgen.outputs\nsteps.uuidgen.running-->steps.uuidgen.outputs\nsteps.uuidgen.starting-->steps.uuidgen.starting.started\nsteps.uuidgen.starting-->steps.uuidgen.running\n
flowchart LR\nsteps.uuidgen.enabling-->steps.uuidgen.starting\nsteps.uuidgen.enabling-->steps.uuidgen.disabled\nsteps.uuidgen.enabling-->steps.uuidgen.enabling.resolved\nsteps.uuidgen.outputs-->steps.uuidgen.outputs.success\nsteps.example.cancelled-->steps.example.outputs\nsteps.example.outputs.success-->outputs.success\ninput-->steps.example.starting\nsteps.uuidgen.disabled-->steps.uuidgen.disabled.output\nsteps.uuidgen.outputs.success-->outputs.success\nsteps.example.disabled-->steps.example.disabled.output\nsteps.example.running-->steps.example.outputs\nsteps.example.enabling-->steps.example.enabling.resolved\nsteps.example.enabling-->steps.example.starting\nsteps.example.enabling-->steps.example.disabled\nsteps.example.starting-->steps.example.starting.started\nsteps.example.starting-->steps.example.running\nsteps.example.deploy-->steps.example.starting\nsteps.uuidgen.deploy-->steps.uuidgen.starting\nsteps.example.outputs-->steps.example.outputs.success\nsteps.uuidgen.cancelled-->steps.uuidgen.outputs\nsteps.uuidgen.running-->steps.uuidgen.outputs\nsteps.uuidgen.starting-->steps.uuidgen.starting.started\nsteps.uuidgen.starting-->steps.uuidgen.running
"},{"location":"arcaflow/getting-started/#running-plugins","title":"Running Plugins","text":"Workflow steps are run via plugins, which are delivered as containers. The Arcalot community maintains an ever-growing list of official plugins, which are version-controlled and hosted in our Quay.io repository.
Plugins are designed to run independent of an Arcaflow workflow. All plugins have schema definitions for their inputs and outputs, and they perform data validation against those schemas when run. Plugins also have one or more steps, and when there are multiple steps we always need to specify which step we want to run.
Tip
Plugin steps are the fundamental building blocks for workflows.
Let\u2019s take a look at the schema for the example plugin. Passing the --schema
parameter to the plugin will return the complete schema in YAML format.
PodmanDocker podman run --rm quay.io/arcalot/arcaflow-plugin-example --schema\n
docker run --rm quay.io/arcalot/arcaflow-plugin-example --schema\n
Here we see the example plugin has one step called hello-world
, which has schemas for both its inputs and outputs.
example plugin schema YAMLsteps:\n hello-world:\n display:\n description: Says hello :)\n name: Hello world!\n id: hello-world\n input:\n objects:\n FullName:\n id: FullName\n properties:\n first_name:\n display:\n name: First name\n examples:\n - '\"Arca\"'\n required: true\n type:\n min: 1\n pattern: ^[a-zA-Z]+$\n type_id: string\n last_name:\n display:\n name: Last name\n examples:\n - '\"Lot\"'\n required: true\n type:\n min: 1\n pattern: ^[a-zA-Z]+$\n type_id: string\n InputParams:\n id: InputParams\n properties:\n name:\n display:\n description: Who do we say hello to?\n name: Name\n examples:\n - '{\"_type\": \"fullname\", \"first_name\": \"Arca\", \"last_name\": \"Lot\"}'\n - '{\"_type\": \"nickname\", \"nick\": \"Arcalot\"}'\n required: true\n type:\n discriminator_field_name: _type\n type_id: one_of_string\n types:\n fullname:\n display:\n name: Full name\n id: FullName\n type_id: ref\n nickname:\n display:\n name: Nick\n id: Nickname\n type_id: ref\n Nickname:\n id: Nickname\n properties:\n nick:\n display:\n name: Nickname\n examples:\n - '\"Arcalot\"'\n required: true\n type:\n min: 1\n pattern: ^[a-zA-Z]+$\n type_id: string\n root: InputParams\n outputs:\n error:\n error: false\n schema:\n objects:\n ErrorOutput:\n id: ErrorOutput\n properties:\n error:\n display: {}\n required: true\n type:\n type_id: string\n root: ErrorOutput\n success:\n error: false\n schema:\n objects:\n SuccessOutput:\n id: SuccessOutput\n properties:\n message:\n display: {}\n required: true\n type:\n type_id: string\n root: SuccessOutput\n
The plugin schema can also be returned in JSON format, in which case you must specify whether to return the input
or output
schema.
PodmanDocker podman run --rm quay.io/arcalot/arcaflow-plugin-example --json-schema output\n
docker run --rm quay.io/arcalot/arcaflow-plugin-example --json-schema output\n
example plugin output schema JSON{\n \"$id\": \"hello-world\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"title\": \"Hello world! outputs\",\n \"description\": \"Says hello :)\",\n \"oneof\": [\n {\n \"output_id\": {\n \"type\": \"string\",\n \"const\": \"success\"\n },\n \"output_data\": {\n \"type\": \"object\",\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"message\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n }\n },\n {\n \"output_id\": {\n \"type\": \"string\",\n \"const\": \"error\"\n },\n \"output_data\": {\n \"type\": \"object\",\n \"properties\": {\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"error\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n }\n }\n ],\n \"$defs\": {\n \"SuccessOutput\": {\n \"type\": \"object\",\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"message\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n },\n \"ErrorOutput\": {\n \"type\": \"object\",\n \"properties\": {\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"error\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n }\n }\n}\n
A plugin takes its input as a file, but because it runs as a container, it looks for the input file in the context of the container. This means you either need to bind-mount the input file to the container, or, as in this example, pipe the input value to the plugin\u2019s file input.
input.yamlname:\n _type: nickname\n nick: Arcalot\n
Note
In order to pass the input to the container via redirection or pipe, you must pass the -i, --interactive
parameter.
Podman with pipePodman with bind mountDocker with pipeDocker with bind mount cat input.yaml | podman run -i --rm quay.io/arcalot/arcaflow-plugin-example -f -\n
podman run --rm -v ${PWD}/input.yaml:/input.yaml:z quay.io/arcalot/arcaflow-plugin-example -f /input.yaml\n
cat input.yaml | docker run -i --rm quay.io/arcalot/arcaflow-plugin-example -f -\n
docker run --rm -v ${PWD}/input.yaml:/input.yaml:z quay.io/arcalot/arcaflow-plugin-example -f /input.yaml\n
example plugin return YAMLoutput_id: success\noutput_data:\n message: Hello, Arcalot!\ndebug_logs: ''\n
Now let\u2019s generate a UUID with the utilities plugin. This plugin has multiple steps, so we need to specify which step to run. The step also requires no input, so we pass it an empty object.
Note
An input object is always required, even if a plugin step does not require input parameters.
PodmanDocker echo '{}' | podman run -i --rm quay.io/arcalot/arcaflow-plugin-utilities -s uuid -f -\n
echo '{}' | docker run -i --rm quay.io/arcalot/arcaflow-plugin-utilities -s uuid -f -\n
utilities plugin UUID step return YAMLoutput_id: success\noutput_data:\n uuid: bb08484f-6263-4317-9162-be2ae846b438\ndebug_logs: ''\n
Learn more about plugin schemas \u00bb
"},{"location":"arcaflow/getting-started/#writing-plugins","title":"Writing Plugins","text":"Of course you may have specific needs and want to author your own plugins. To aid with this, we provide SDKs in popular languages. Let\u2019s create a simple hello-world plugin using the Python SDK. We\u2019ll publish the code here, and you can find the details in the Python plugin guide.
plugin.py#!/usr/local/bin/python3\nfrom dataclasses import dataclass\nimport sys\nfrom arcaflow_plugin_sdk import plugin\n\n\n@dataclass\nclass InputParams:\n name: str\n\n\n@dataclass\nclass SuccessOutput:\n message: str\n\n\n@plugin.step(\n id=\"hello-world\",\n name=\"Hello world!\",\n description=\"Says hello :)\",\n outputs={\"success\": SuccessOutput},\n)\ndef hello_world(params: InputParams):\n return \"success\", SuccessOutput(f\"Hello, {params.name}\")\n\n\nif __name__ == \"__main__\":\n sys.exit(\n plugin.run(\n plugin.build_schema(\n hello_world,\n )\n )\n )\n
Learn more about writing Python plugins \u00bb
Next, let\u2019s create a Dockerfile
and build a container image:
Dockerfile
FROM quay.io/arcalot/arcaflow-plugin-baseimage-python-osbase\n\nADD plugin.py /\nRUN python -m pip install arcaflow_plugin_sdk\n\nENTRYPOINT [\"python\", \"/plugin.py\"]\nCMD []\n
We can now build the plugin container. PodmanDocker podman build -t example-plugin .\n
docker build -t example-plugin .\n
And finally we can run our new plugin.
PodmanDocker echo \"name: Arca Lot\" | podman run -i --rm example-plugin -f -\n
echo \"name: Arca Lot\" | docker run -i --rm example-plugin -f -\n
example plugin output YAMLoutput_id: success\noutput_data:\n message: Hello, Arca Lot\ndebug_logs: ''\n
Learn more about Packaging plugins
"},{"location":"arcaflow/getting-started/#next-steps","title":"Next steps","text":"Congratulations, you are now an Arcaflow user! Here are some things you can do next to start working with plugins and workflows:
- See our repositories of community-supported plugins \u00bb
- Get our latest plugin container builds from quay.io \u00bb
- Experiment with more advanced example workflows \u00bb
"},{"location":"arcaflow/getting-started/#keep-learning","title":"Keep learning","text":"Hungry for more? Keep digging into our docs::
- Learn more about the concepts behind Arcaflow \u00bb
- Learn how to set up Arcaflow \u00bb
- Learn how to create plugins \u00bb
- Learn how to create workflows \u00bb
Contribute to Arcaflow \u00bb
"},{"location":"arcaflow/concepts/","title":"Concepts","text":"This section of the documentation deals with theoretical concepts around Arcaflow. Fear not, it\u2019s not a university exam, but simply a description on how we designed Arcaflow the way we did and why we did so.
Architecture Get started with a primer on the core architecture of Arcaflow.
Read more about architecture \u00bb
Typing We believe in strong and static typing to avoid bugs, so Arcaflow has its own typing system.
Read more about typing \u00bb
Plugins Arcaflow is interoperable between several programming languages. Currently, we provide SDKs for Python and Go.
Read more about plugins \u00bb
Workflows Arcaflow runs several steps and connects them together into a workflow.
Read more about workflows \u00bb
Flow control (WIP) In the future, we want to add the ability to perform loops, dynamic parallelization, etc.
Read more about flow control \u00bb
"},{"location":"arcaflow/concepts/architecture/","title":"Arcaflow architecture","text":"The Arcaflow architecture consists of the following 2 keys elements:
- Plugins
- The Engine
"},{"location":"arcaflow/concepts/architecture/#engine","title":"Engine","text":"The engine is responsible for the orchestration of the workflow steps. It has several duties:
- Provide schemas for workflow files, read workflows and construct execution graphs.
- Type-check the execution graphs to make sure that the data transfers between steps are type-safe.
- Orchestrate plugin execution with Docker, Podman and Kubernetes.
- Execute the workflow, following the workflow rules.
The engine itself is designed to be run from a command line interface, possibly as a webserver, but is not designed to run in a redundant fashion. Instead of implementing redundancy itself, the engine will receive support to execute workflows in third party systems, such as Kafka.
A stretch goal for the engine is to make it fully embeddable, possibly with in-binary workflows and execution images to make them easily to ship in network-restricted environments.
"},{"location":"arcaflow/concepts/architecture/#plugins","title":"Plugins","text":"Plugins provide execution for one or more steps for a workflow. The job of a plugin is to do one thing and do it well. A plugin provides a thin layer over third party tools, or its own implementation of features. Its main job is to provide accurate input and output schema information to the engine and transform the data as needed.
For example, a plugin may output unformatted text, which a plugin has to parse and build a machine-readable data structure for that information. This reformatting of data allows the engine to pipe data between steps and reliably check the data for faults.
The current plan is to provide plugin SDKs for Python, GO, and Rust (in that order).
"},{"location":"arcaflow/concepts/architecture/#typing","title":"Typing","text":"A core element of the Arcaflow system is the typing system. Each plugin and the engine itself will provide a machine-readable data structure that describes what inputs are expected and what outputs may be produced. If you are familiar with JSON schema or OpenAPI, this is similar, and Arcaflow can produce those schema documents. However, the Arcaflow system is stricter than those industry standards to optimize for performance and simpler implementation in all supported programming languages.
"},{"location":"arcaflow/concepts/architecture/#executing-workflows","title":"Executing workflows","text":"Arcaflow workflows are orchestrated in the Engine, while plugins can be run locally or remotely on container engines. This lends itself to a Git-based workflow:
flowchart LR\n subgraph laptop[Your laptop]\n direction LR\n\n ui(UI)\n engine(Engine)\n git(Git)\n\n ui -- Workflow --> engine\n ui -- Workflow --> git -- Workflow --> engine\n engine -- Execution results --> ui\n end\n\n subgraph docker[Docker/Podman<br>on your laptop]\n direction LR\n\n plugins1(Plugin)\n\n engine -- Step execution --> plugins1\n end\n engine -- Launch plugin --> docker\n\n subgraph k8s[Kubernetes]\n direction LR\n\n plugins2(Plugin)\n\n engine -- Step execution --> plugins2\n end\n engine -- Launch plugin --> k8s\n\n apis(Other APIs)\n plugins1 --> apis\n plugins2 --> apis
"},{"location":"arcaflow/concepts/plugins/","title":"Arcaflow plugins","text":"Arcaflow is designed as an interoperable system between programming languages. Therefore, plugins are started as external processes and the communication with the plugin takes place over its standard input and output. The Arcaflow Engine passes data between the plugins as required by the workflow file.
In the vast majority of cases, plugins run inside a container, while the Arcaflow Engine itself does not. This allows Arcaflow to pass data between several Kubernetes clusters, local plugins, or even run plugins via Podman over SSH. These capabilities are built into the Arcaflow Engine with the help of deployers.
Since Arcaflow has an internal typing system, each plugin must declare at the start what input data it requires and what outputs it produces. This allows the Engine to verify that the workflow can be run, and that no invalid data is being used. If invalid data is detected, the workflow is aborted to prevent latent defects in the data.
In summary, you can think of Arcaflow as a strongly (and at some time in the future possibly statically) typed system for executing workflows, where individual plugins run in containers across several systems.
"},{"location":"arcaflow/concepts/typing/","title":"Typing system","text":"Let\u2019s say you are creating a system that measures performance. But, uh-oh! A bug has struck! Instead of returning a number, a plugin returns an empty string. Would you want that converted to a numeric 0
for a metric? Or worse yet, would you want a negative number resulting from a bug to make it into your metrics? Would you want to collect metrics for years just to find out they are all wrong?
If the answer is no, then the typing system is here to help. Each plugin or workflow in Arcaflow is required to explicitly state what data types it accepts for its fields, and what their boundaries are. When a plugin then violates its own rules, the engine makes sure that corrupt data isn\u2019t used any further.
For example, let\u2019s look at the definition of an integer:
type_id: integer\nmin: 10\nmax: 128\n
It\u2019s so simple, but it already prevents a lot of bugs: non-integers, numbers out of range.
But wait! A typing system can do more for you. For example, we can automatically generate a nice documentation from it. Let\u2019s take this object as an example:
type_id: object\nid: name\nproperties:\n name:\n type:\n type_id: string\n min: 1\n max: 256\n display:\n name: \"Name\"\n description: \"The name of the user.\"\n icon: |\n <svg ...></svg>\n
That\u2019s all it takes to render a nice form field or automatic documentation. You can read more about creating types in the plugins section or the workflows section, or see the complete typing reference in the Contributing guide.
"},{"location":"arcaflow/concepts/workflows/","title":"Arcaflow Workflows (concept)","text":"Tip
This document describes the concept of Arcaflow Workflows. We describe the process of writing a workflow in this section
"},{"location":"arcaflow/concepts/workflows/#steps","title":"Steps","text":"Workflows are a way to describe a sequence or parallel execution of individual steps. The steps are provided exclusively by plugins. The simplest workflow looks like this:
stateDiagram-v2\n [*] --> Step\n Step --> [*]
However, this is only true if the step only has one output. Most steps will at least have two possible outputs, for success and error states:
stateDiagram-v2\n [*] --> Step\n Step --> [*]: yes\n Step --> [*]: no
Plugins can declare as many outputs as needed, with custom names. The workflow engine doesn\u2019t make a distinction based on the names, all outputs are treated equal for execution.
An important rule is that one step must always end in exactly one output. No step must end without an output, and no step can end in more than one output. This provides a mechanism to direct the flow of the workflow execution.
Plugins must also explicitly declare what parameters they expect as input for the step, and the data types of these and what parameters they will produce as output.
"},{"location":"arcaflow/concepts/workflows/#interconnecting-steps","title":"Interconnecting steps","text":"When two steps are connected, they will be executed after each other:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n Step1 --> Step2\n Step2 --> [*]
Similarly, when two steps are not directly connected, they may be executed in parallel:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n [*] --> Step2\n Step1 --> [*]\n Step2 --> [*]
You can use the interconnection to direct the flow of step outputs:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n Step3: Step 3\n [*] --> Step1\n Step1 --> Step2: success\n Step1 --> Step3: error\n Step2 --> [*]\n Step3 --> [*]
"},{"location":"arcaflow/concepts/workflows/#passing-data-between-steps","title":"Passing data between steps","text":"When two steps are connected, you have the ability to pass data between them. Emblematically described:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n Step1 --> Step2: input_1 = $.steps.step1.outputs.success\n Step2 --> [*]
The data type of the input on Step 2 in this case must match the result of the expression. If the data type does not match, the workflow will not be executed.
"},{"location":"arcaflow/concepts/workflows/#undefined-inputs","title":"Undefined inputs","text":"Step inputs can either be required or optional. When a step input is required, it must be configured or the workflow will fail to execute. However, there are cases when the inputs cannot be determined from previous steps. In this case, the workflow start can be connected and the required inputs can be obtained from the user when running the workflow:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n [*] --> Step2: input_1 = $.input.option_1\n Step1 --> Step2: input_2 = $.steps.step1.outputs.success\n Step2 --> [*]
This is typically the case when credentials, such as database access, etc. are required.
"},{"location":"arcaflow/concepts/workflows/#outputs","title":"Outputs","text":"The output for each step is preserved for later inspection. However, the workflow can explicitly declare outputs. These outputs are usable in scripted environments as a direct output of the workflow:
stateDiagram-v2\n [*] --> Step\n Step --> [*]: output_1 = $.steps.step1.outputs.success
Background processes
Each plugin will only be invoked once, allowing plugins to run background processes, such as server applications. The plugins must handle SIGINT and SIGTERM events properly.
"},{"location":"arcaflow/concepts/workflows/#flow-control-wip","title":"Flow control (WIP)","text":"The workflow contains several flow control operations. These flow control operations are not implemented by plugins, but are part of the workflow engine itself.
"},{"location":"arcaflow/concepts/workflows/#foreach","title":"Foreach","text":"The foreach flow control allows you to loop over a sub-workflow with a list of input objects.
stateDiagram-v2\n [*] --> ForEach\n state ForEach {\n [*] --> loop_list_input\n loop_list_input --> sub_workflow\n sub_workflow --> loop_list_input\n state sub_workflow {\n [*] --> Step1\n Step1 --> [*]\n }\n sub_workflow --> [*]: Sub Output\n }\n ForEach --> [*]: Output
Warning
The features below are in-development and not yet implemented in the released codebase.
"},{"location":"arcaflow/concepts/workflows/#abort","title":"Abort","text":"The abort flow control is a quick way to exit out of a workflow. This is useful when entering a terminal error state and the workflow output data would be useless anyway.
stateDiagram-v2\n [*] --> Step1\n Step1 --> Abort: Output 1\n Step1 --> Step2: Output 2\n Step2 --> [*]
However, this is only required if you want to abort the workflow immediately. If you want an error case to result in the workflow failing, but whatever steps can be finished being finished, you can leave error outputs unconnected.
"},{"location":"arcaflow/concepts/workflows/#do-while","title":"Do-while","text":"A do-while block will execute the steps in it as long as a certain condition is met. The condition is derived from the output of the step or steps executed inside the loop:
stateDiagram-v2\n [*] --> DoWhile\n state DoWhile {\n [*] --> Step1\n Step1 --> [*]: output_1_condition=$.step1.output_1.finished == false \n }\n DoWhile --> [*]
If the step declares multiple outputs, multiple conditions are possible. The do-while block will also have multiple outputs:
stateDiagram-v2\n [*] --> DoWhile\n state DoWhile {\n [*] --> Step1\n Step1 --> [*]: Output 1 condition\n Step1 --> [*]: Output 2 condition \n }\n DoWhile --> [*]: Output 1\n DoWhile --> [*]: Output 2
You may decide to only allow exit from a loop if one of the two outputs is satisfied:
stateDiagram-v2\n [*] --> DoWhile\n state DoWhile {\n [*] --> Step1\n Step1 --> Step1: Output 1\n Step1 --> [*]: Output 2\n }\n DoWhile --> [*]: Output 1
"},{"location":"arcaflow/concepts/workflows/#condition","title":"Condition","text":"A condition is a flow control operation that redirects the flow one way or another based on an expression. You can also create multiple branches to create a switch-case effect.
stateDiagram-v2\n state if_state <<choice>>\n Step1: Step 1\n [*] --> Step1\n Step1 --> if_state\n Step2: Step 2\n Step3: Step 3\n if_state --> Step2: $.step1.output_1 == true\n if_state --> Step3: $.step1.output_1 == false
"},{"location":"arcaflow/concepts/workflows/#multiply","title":"Multiply","text":"The multiply flow control operation is useful when you need to dynamically execute sub-workflows in parallel based on an input condition. You can, for example, use this to run a workflow step on multiple or all Kubernetes nodes.
stateDiagram-v2\n Lookup: Lookup Kubernetes hosts\n [*] --> Lookup\n Lookup --> Multiply\n state Multiply {\n [*] --> Stresstest\n Stresstest --> [*]\n }\n Multiply --> [*]
The output of a Multiply operation will be a map, keyed with a string that is configured from the input.
Tip
You can think of a Multiply step like a for-each loop, but the steps being executed in parallel.
"},{"location":"arcaflow/concepts/workflows/#synchronize","title":"Synchronize","text":"The synchronize step attempts to synchronize the execution of subsequent steps for a specified key. The key must be a constant and cannot be obtained from an input expression.
stateDiagram-v2\n [*] --> Step1\n [*] --> Step2\n Synchronize1: Synchronize (key=a)\n Synchronize2: Synchronize (key=a)\n Step1 --> Synchronize1\n Step2 --> Synchronize2\n Synchronize1 --> Step3\n Synchronize2 --> Step4\n Step3 --> [*]\n Step4 --> [*]
"},{"location":"arcaflow/contributing/","title":"Contributing to Arcaflow","text":"First of all, welcome to the Arca Lot! Whether you are a beginner or a seasoned veteran, your contributions are most appreciated. Thank you!
Now, let\u2019s get you started. There are a number of ways you can contribute on GitHub, please check the Arcaflow project board for open issues. Additionally, here are a few repos you can contribute to:
Repository What you can do here arcalot.github.io Improve the documentation arcaflow-plugin-sdk-go Improve the Go SDK arcaflow-plugin-sdk-python Improve the Python SDK arcaflow-engine Improve the Arcaflow Engine arcaflow-engine-deployer-kubernetes Improve the Kubernetes deployment of plugins arcaflow-engine-deployer-docker Improve the Docker deployment of plugins arcaflow-engine-deployer-podman Improve the Podman deployment of plugins arcaflow-expressions Improve the Arcaflow expression language arcaflow-plugin-image-builder Improve the Arcaflow plugin packaging arcaflow-plugin-* Improve the officially supported plugins If you want to contribute regularly, why not join the Arcalot Round Table by reading our charter and signing up as a member? That way you get a voice in the decisions we make!
"},{"location":"arcaflow/contributing/#license","title":"License","text":"All code in Arcaflow is licensed under the Apache 2.0 license. The documentation is licensed under CC-BY-4.0. Please make sure you read and understand these licenses before contributing. If you are contributing on behalf of your employer, please make sure you have permission to do so.
"},{"location":"arcaflow/contributing/#principles","title":"Principles","text":"While we don\u2019t deal in absolutes (only a Sith would do that) we hold ourselves to a few key principles. There are plenty of things where we could do better in these areas, so if you find something, please open an issue. It\u2019s important!
"},{"location":"arcaflow/contributing/#the-principle-of-the-least-surprise","title":"The principle of the least surprise","text":"Sometimes, things are just hard to make user-friendly. If presented with two choices, we will always pick the one that doesn\u2019t break expectations. What would an average user expect to happen without reading the documentation? If something surprised you, please open a bug.
"},{"location":"arcaflow/contributing/#the-principle-of-nice-error-messages","title":"The principle of nice error messages","text":"When using Arcaflow, you should never be confronted with a stack trace. Error messages should always explain what went wrong and how to fix it. We know, this is a tall order, but if you see an error message that is not helpful, please open a bug.
"},{"location":"arcaflow/contributing/#the-principle-of-intern-friendliness","title":"The principle of intern-friendliness","text":"There is enough software out in the wild that requires months of training and is really hard to get into. Arcaflow isn\u2019t the easiest to learn either, see the whole typing system thing, but nevertheless, the software should be written in such a way that an intern with minimal training can sit down and do something useful with it. If something is unnecessarily hard or undocumented, you guessed it, please open a bug.
"},{"location":"arcaflow/contributing/#the-principle-of-typing","title":"The principle of typing","text":"We believe that strong and static typing can save us from bugs. This applies to programming languages just as much as it applies to workflows. We aim to make a system tell us that something is wrong before we spent several hours running it.
"},{"location":"arcaflow/contributing/#the-principle-of-testing","title":"The principle of testing","text":"Bugs? Yeah, we have those, and we want fewer of them. Since we are a community effort, we can\u2019t afford a large QA team to test through everything manually before a release. Therefore, it\u2019s doubly important that we have automated tests that run on every change. Furthermore, we want our tests to run quickly and without additional setup time. You should be able to run go test
or python -m unittest discover
and get a result within a few seconds at most. This makes it more likely that a contributor will run the tests and contribute new tests instead of waiting for CI to sort it out.
"},{"location":"arcaflow/contributing/#the-principle-of-small-piles","title":"The principle of small piles","text":"Every software is\u2026 pardon our French: crap. Ours is no exception. The difference is how big and how stinky the piles are. We aim to make the piles small, well-defined and as stink-less as possible. If we need to replace a pile with another pile, it should be easy to do so.
Translated to software engineering, we create APIs between our piles components. These APIs can be in the form of code, or in the form of a GitHub Actions workflow. A non-leaky API helps us replace one side of the API without touching the other.
"},{"location":"arcaflow/contributing/#the-principle-of-kindness-to-our-future-self","title":"The principle of kindness to our future self","text":"Writing code should be fun, most of us got into this industry because we enjoyed creating something. We want to keep this joy of creation. What kills the enthusiasm fastest is having to slog through endless pieces of obtuse code, spending hours and hours trying to accomplish a one-line change. When we write code, we want to be kind to our future selves. That\u2019s why we not only write documentation and tests for our users, we also create these for ourselves and our peers.
"},{"location":"arcaflow/contributing/deployers/","title":"Arcaflow Deployers Development Guide","text":"The Arcaflow Engine relies on deployers to execute containers. Deployers provide a binary-safe transparent tunnel of communication between a plugin and the engine. (Typically, this will be done via standard input/output, but other deployers are possible.)
The Engine and the plugin communicate via the Arcaflow Transport Protocol over this tunnel, but the deployer is unaware of the method of this communication.
Deployers are written in Go and must implement the deployer interface. Deployers are not dynamically pluggable, they must also be added to the engine code to be usable.
"},{"location":"arcaflow/contributing/engine/","title":"Arcaflow Engine Development Guide","text":"Warning
The engine is currently undergoing a major refactor. This page describes the engine post-refactor.
The Arcaflow engine is responsible for parsing a YAML workflow and executing it. It goes through several phases during execution.
"},{"location":"arcaflow/contributing/engine/#yaml-loading-phase","title":"YAML loading phase","text":"During the YAML loading phase, the engine loads the workflow YAML as raw data containing YAML nodes. We need the raw YAML nodes to access the YAML tags, which we use to turn the structure into expressions. The resulting data structure of this phase is a structure consisting of maps, lists, strings, and expression objects.
YAML
YAML, at its core, only knows three data types: maps, lists, and strings. Additionally, each entry can have a tag in the form of !foo
or !!foo
.
"},{"location":"arcaflow/contributing/engine/#basic-workflow-parsing","title":"Basic workflow parsing","text":"Once the YAML is loaded, we can take the data created and parse the workflow. This will validate the input definition and the basic step definitions and provide a more structured data. However, at this point the plugin schemas are not known yet, so any data structure related to steps is accepted as-is.
"},{"location":"arcaflow/contributing/engine/#schema-loading","title":"Schema loading","text":"The engine has an API to provide step types. These step types have the ability to provide a lifecycle and load their schema. In case of plugins, this means that the plugin is fired up briefly and its schema is queried. (See Deployers.)
"},{"location":"arcaflow/contributing/engine/#dag-construction","title":"DAG construction","text":"Once the schema is loaded, the Directed Acyclic Graph can be constructed from the expressions. Each lifecycle stage input is combed for expressions and a DAG is built.
"},{"location":"arcaflow/contributing/engine/#static-code-analysis-future","title":"Static code analysis (future)","text":"The expression library already has the facilities to inspect types, which will, in the future, provide us the ability to perform a static code analysis on the workflow. This will guarantee users that a workflow can be executed without typing problems.
"},{"location":"arcaflow/contributing/engine/#workflow-execution","title":"Workflow execution","text":"When the DAG is complete and contains no cycles, the workflow execution can proceed. The execution cycle queries lifecycle nodes that have no more inbound dependencies and runs the lifecycle. When a lifecycle stage finishes, the corresponding nodes are removed from the DAG, freeing up other nodes for execution.
"},{"location":"arcaflow/contributing/expressions/","title":"Arcaflow Expressions Development Guide","text":"The expressions library provides the engine and other potential users with a simple way to compile expressions and provide typing information about an expression result.
The library consists of two parts: the internal parser/AST and the API layer.
"},{"location":"arcaflow/contributing/expressions/#the-parser-ast","title":"The Parser / AST","text":"The expressions parser constructs an Abstract Syntax Tree from the expression which can then be walked by the API layer. The AST consists of the following node types:
"},{"location":"arcaflow/contributing/expressions/#dot-notation","title":"Dot Notation","text":"Let\u2019s say you have an expression foo.bar
. The dot notation node is the dot in the middle. The left subtree of the dot will be the entire expression left of the dot, while the right subtree will be everything to the right.
"},{"location":"arcaflow/contributing/expressions/#bracket-expression","title":"Bracket Expression","text":"Bracket expressions are expressions in the form of foo[bar]
. The left subtree will represent the expression to the left of the brackets (foo
in the example), while the right subtree will represent the subexpression within the brackets (bar
in the example).
"},{"location":"arcaflow/contributing/expressions/#binary-operations","title":"Binary Operations","text":"Binary operations include all of the operations that have a left and right subtree that do not have a special node representing them (dot notation and bracket expression are examples of special cases). Binary operations are represented by a node containing an operation and the subtrees to which the operation is applied.
"},{"location":"arcaflow/contributing/expressions/#unary-operations","title":"Unary Operations","text":"Unary operations include boolean complement !
and numeric negation -
. Unary operations are represented by a node containing an operation and the subtree to which the operation is applied. Unlike binary operations, unary operations have only one subtree.
"},{"location":"arcaflow/contributing/expressions/#identifiers","title":"Identifiers","text":"Identifiers come in two forms:
$
references the root of the data structure. - A plain string identifier from a token matching the regular expression
^\\w+$
. This may be used for accessing object fields or as function identifiers.
"},{"location":"arcaflow/contributing/expressions/#the-api-layer","title":"The API Layer","text":"The API layer provides three functions:
- Evaluate an expression against a data structure without type checking
- Provide dependency information of an expression
- Return the resulting type info of an expression when given a schema
All three functions walk the AST above and construct the required data.
"},{"location":"arcaflow/contributing/plugin-protocol/","title":"Arcaflow Plugin protocol specification (ATP)","text":"Arcaflow runs plugins locally in a container using Docker or Podman, or remotely in Kubernetes. Each plugin must be containerized and communicates with the engine over standard input/output. This document outlines the protocol the engine and the plugins use to communicate.
Hint
You do not need this page if you only intend to implement a plugin with the SDK!
"},{"location":"arcaflow/contributing/plugin-protocol/#execution-model","title":"Execution model","text":"A single plugin execution is intended to run a single task and not more. This simplifies the code since there is no need to try and clean up after each task. Each plugin is executed in a container and must communicate with the engine over standard input/output. Furthermore, the plugin must add a handler for SIGTERM
and properly clean up if there are services running in the background.
Each plugin is executed at the start of the workflow, or workflow block, and is terminated only at the end of the current workflow or workflow block. The plugin can safely rely on being able to start a service in the background and then keeping it running until the SIGTERM
comes to shut down the container.
However, the plugin must, under no circumstances, start doing work until the engine sends the command to do so. This includes starting any services inside the container or outside. This restriction is necessary to be able to launch the plugin with minimal resource consumption locally on the engine host to fetch the schema.
The plugin execution is divided into three major steps.
- When the plugin is started, it must output the current plugin protocol version and its schema to the standard output. The engine will read this output from the container logs.
- When it is time to start the work, the engine will send the desired step ID with its input parameters over the standard input. The plugin acknowledges this and starts to work. When the work is complete, the plugin must automatically output the results to the standard output.
- When a shutdown is desired, the engine will send a
SIGTERM
to the plugin. The plugin has up to 30 seconds to shut down. The SIGTERM
may come at any time, even while the work is still running, and the plugin must appropriately shut down. If the work is not complete, it is important that the plugin does not send error output to STDOUT. If the plugin fails to stop by itself within 30 seconds, the plugin container is forcefully stopped.
"},{"location":"arcaflow/contributing/plugin-protocol/#protocol","title":"Protocol","text":"As a data transport protocol, we use CBOR messages RFC 8949 back to back due to their self-delimiting nature. This section provides the entire protocol as JSON schema below.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-0-the-start-output-message","title":"Step 0: The \u201cstart output\u201d message","text":"Because Kubernetes has no clean way of capturing an output right at the start, the initial step of the plugin execution involves the engine sending an empty CBOR message (None
or Nil
) to the plugin. This indicates, that the plugin may start its output now.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-1-hello-message","title":"Step 1: Hello message","text":"The \u201cHello\u201d message is a way for the plugin to introduce itself and present its steps and schema. Transcribed to JSON, a message of this kind would look as follows:
{\n \"version\": 1,\n \"steps\": {\n \"step-id-1\": {\n \"name\": \"Step 1\",\n \"description\": \"This is the first step\",\n \"input\": {\n \"schema\": {\n // Input schema\n }\n },\n \"outputs\": {\n \"output-id-1\": {\n \"name\": \"Name for this output kind\",\n \"description\": \"Description for this output\",\n \"schema\": {\n // Output schema\n }\n }\n }\n }\n }\n}\n
The schemas must describe the data structure the plugin expects. For a simple hello world input would look as follows:
{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n }\n }\n}\n
The full schema is described below in the Schema section.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-2-start-work-message","title":"Step 2: Start work message","text":"The \u201cstart work\u201d message has the following parameters in CBOR:
{\n \"id\": \"id-of-the-step-to-execute\",\n \"config\": {\n // Input parameters according to schema here\n }\n}\n
The plugin must respond with a CBOR message of the following format:
{\n \"status\": \"started\"\n}\n
"},{"location":"arcaflow/contributing/plugin-protocol/#step-3a-crash","title":"Step 3/a: Crash","text":"If the plugin execution ended unexpectedly, the plugin should crash and output a reasonable error message to the standard error. The plugin must exit with a non-zero exit status to notify the engine that the execution failed.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-3b-output","title":"Step 3/b: Output","text":"When the plugin has executed successfully, it must emit a CBOR message to the standard output:
{\n \"output_id\": \"id-of-the-declared-output\",\n \"output_data\": {\n // Result data of the plugin\n },\n \"debug_logs\": \"Unstructured logs here for debugging as a string.\"\n}\n
"},{"location":"arcaflow/contributing/plugin-protocol/#schema","title":"Schema","text":"This section contains the exact schema that the plugin sends to the engine.
Type: scope
Root object: Schema Properties steps (map[string, reference[Step]]
) Name: Steps Description: Steps this schema supports. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Step]
Referenced object: Step (see in the Objects section below) Objects AnySchema (object
) Type: object
Properties None
BoolSchema (object
) Type: object
Properties None
Display (object
) Type: object
Properties description (string
) Name: Description Description: Description for this item if needed. Required: No Minimum: 1 Examples \"Please select the fruit you would like.\"\n
icon (string
) Name: Icon Description: SVG icon for this item. Must have the declared size of 64x64, must not include additional namespaces, and must not reference external resources. Required: No Minimum: 1 Examples \"<svg ...></svg>\"\n
name (string
) Name: Name Description: Short text serving as a name or title for this item. Required: No Minimum: 1 Examples \"Fruit\"\n
Float (object
) Type: object
Properties max (float
) Name: Maximum Description: Maximum value for this float (inclusive). Required: No Examples 16.0\n
min (float
) Name: Minimum Description: Minimum value for this float (inclusive). Required: No Examples 5.0\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
Int (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum value for this int (inclusive). Required: No Minimum: 0 Examples 5\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
IntEnum (object
) Type: object
Properties units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
values (map[int, reference[Display]]
) Name: Values Description: Possible values for this field. Required: Yes | Minimum items: | 1 |\n
Key type Type: int
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\"1024\": {\"name\": \"kB\"}, \"1048576\": {\"name\": \"MB\"}}\n
List (object
) Type: object
Properties items (one of[string]
) Name: Items Description: ReflectedType definition for items in this list. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
Map (object
) Type: object
Properties keys (one of[string]
) Name: Keys Description: ReflectedType definition for keys in this map. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
values (one of[string]
) Name: Values Description: ReflectedType definition for values in this map. Required: No Object (object
) Type: object
Properties id (string
) Name: ID Description: Unique identifier for this object within the current scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
properties (map[string, reference[Property]]
) Name: Properties Description: Properties of this object. Required: Yes Key type Type: string
Minimum: 1 Value type Type: reference[Property]
Referenced object: Property (see in the Objects section below) OneOfIntSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[int, one of[string]]
) Name: Types Required: No Key type Type: int
Value type Type: one of[string]
OneOfStringSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[string, one of[string]]
) Name: Types Required: No Key type Type: string
Value type Type: one of[string]
Pattern (object
) Type: object
Properties None
Property (object
) Type: object
Properties conflicts (list[string]
) Name: Conflicts Description: The current property cannot be set if any of the listed properties are set. Required: No List Items Type: string
default (string
) Name: Default Description: Default value for this property in JSON encoding. The value must be unserializable by the type specified in the type field. Required: No display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) examples (list[string]
) Name: Examples Description: Example values for this property, encoded as JSON. Required: No List Items Type: string
required (bool
) Name: Required Description: When set to true, the value for this field must be provided under all circumstances. Required: No Defaulttrue\n
required_if (list[string]
) Name: Required if Description: Sets the current property to required if any of the properties in this list are set. Required: No List Items Type: string
required_if_not (list[string]
) Name: Required if not Description: Sets the current property to be required if none of the properties in this list are set. Required: No List Items Type: string
type (one of[string]
) Name: Type Description: Type definition for this field. Required: Yes Ref (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) id (string
) Name: ID Description: Referenced object ID. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Schema (object
) Type: object
Properties steps (map[string, reference[Step]]
) Name: Steps Description: Steps this schema supports. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Step]
Referenced object: Step (see in the Objects section below) Scope (object
) Type: object
Properties objects (map[string, reference[Object]]
) Name: Objects Description: A set of referencable objects. These objects may contain references themselves. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Object]
Referenced object: Object (see in the Objects section below) root (string
) Name: Root object Description: ID of the root object of the scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Step (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) id (string
) Name: ID Description: Machine identifier for this step. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
input (reference[Scope]
) Name: Input Description: Input data schema. Required: Yes Referenced object: Scope (see in the Objects section below) outputs (map[string, reference[StepOutput]]
) Name: Input Description: Input data schema. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[StepOutput]
Referenced object: StepOutput (see in the Objects section below) StepOutput (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) error (bool
) Name: Error Description: If set to true, this output will be treated as an error output. Required: No Defaultfalse\n
schema (reference[Scope]
) Name: Schema Description: Data schema for this particular output. Required: Yes Referenced object: Scope (see in the Objects section below) String (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 16\n
min (int
) Name: Minimum Description: Minimum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 5\n
pattern (pattern
) Name: Pattern Description: Regular expression this string must match. Required: No Examples \"^[a-zA-Z]+$\"\n
StringEnum (object
) Type: object
Properties values (map[string, reference[Display]]
) Name: Values Description: Mapping where the left side of the map holds the possible value and the right side holds the display value for forms, etc. Required: Yes | Minimum items: | 1 |\n
Key type Type: string
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\n \"apple\": {\n \"name\": \"Apple\"\n },\n \"orange\": {\n \"name\": \"Orange\"\n }\n}\n
Unit (object
) Type: object
Properties name_long_plural (string
) Name: Name long (plural) Description: Longer name for this UnitDefinition in plural form. Required: Yes Examples \"bytes\",\"characters\"\n
name_long_singular (string
) Name: Name long (singular) Description: Longer name for this UnitDefinition in singular form. Required: Yes Examples \"byte\",\"character\"\n
name_short_plural (string
) Name: Name short (plural) Description: Shorter name for this UnitDefinition in plural form. Required: Yes Examples \"B\",\"chars\"\n
name_short_singular (string
) Name: Name short (singular) Description: Shorter name for this UnitDefinition in singular form. Required: Yes Examples \"B\",\"char\"\n
Units (object
) Type: object
Properties base_unit (reference[Unit]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: Yes Referenced object: Unit (see in the Objects section below) Examples {\n \"name_short_singular\": \"B\",\n \"name_short_plural\": \"B\",\n \"name_long_singular\": \"byte\",\n \"name_long_plural\": \"bytes\"\n}\n
multipliers (map[int, reference[Unit]]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: No Key type Type: int
Value type Type: reference[Unit]
Referenced object: Unit (see in the Objects section below) Examples {\n \"1024\": {\n \"name_short_singular\": \"kB\",\n \"name_short_plural\": \"kB\",\n \"name_long_singular\": \"kilobyte\",\n \"name_long_plural\": \"kilobytes\"\n },\n \"1048576\": {\n \"name_short_singular\": \"MB\",\n \"name_short_plural\": \"MB\",\n \"name_long_singular\": \"megabyte\",\n \"name_long_plural\": \"megabytes\"\n }\n}\n
"},{"location":"arcaflow/contributing/typing/","title":"The Arcaflow type system","text":"Arcaflow takes a departure from the classic run-and-pray approach of running workloads and validates workflows before executing them. To do this, Arcaflow starts the plugins as needed before the workflow is run and queries them for their schema. This schema will contain information about what kind of input a plugin requests and what kind of outputs it can produce.
A plugin can support multiple workflow steps and must provide information about the data types in its input and output for each step. A step can have exactly one input format, but may declare more than one output.
The type system is inspired by JSON schema and OpenAPI, but it is more restrictive due to the need to efficiently serialize workloads over various formats.
"},{"location":"arcaflow/contributing/typing/#types","title":"Types","text":"The typing system supports the following data types.
- Objects are key-value pairs where the keys are always a fixed set of strings and values are of various types declared for each key. They are similar to classes in most programming languages. Fields in objects can be optional, which means they will have no value (commonly known as
null
, nil
, or None
), or a default value. - OneOf are a special type that is a union of multiple objects, distinguished by a special field called the discriminator.
- Lists are a sequence of values of the same type. The value type can be any of the other types described in this section. List items must always have a value and cannot be empty (
null
, nil
, or None
). - Maps are key-value pairs that always have fixed types for both keys and values. Maps with mixed keys or values are not supported. Map keys can only be strings, integers, or enums. Map keys and values must always have a value and cannot be empty (
null
, nil
, or None
). - Enums are either strings or integers that can take only a fixed set of values. Enums with mixed value types are not supported.
- Strings are a sequence of bytes.
- Patterns are regular expressions.
- Integers are 64-bit numbers that can take both positive and negative values.
- Floats are 64-bit floating point numbers that can take both positive and negative values.
- Booleans are values of
true
or false
and cannot take any other values. - Scopes and Refs are object-like types that allow you to create circular references (see below).
- Any accepts any primitive type (string, int, float, bool, map, list) but no patterns, objects, etc.
"},{"location":"arcaflow/contributing/typing/#validation","title":"Validation","text":"The typing system also contains more in-depth validation than just simple types:
"},{"location":"arcaflow/contributing/typing/#strings","title":"Strings","text":"Strings can have a minimum or maximum length, as well as validation against a regular expression.
"},{"location":"arcaflow/contributing/typing/#ints-floats","title":"Ints, floats","text":"Number types can have a minimum and maximum value (inclusive).
"},{"location":"arcaflow/contributing/typing/#booleans","title":"Booleans","text":"Boolean types can take a value of either true
or false
, but when unserializing from YAML or JSON formats, strings or int values of true
, yes
, on
, enable
, enabled
, 1
, false
, no
, off
, disable
, disabled
or 0
are also accepted.
"},{"location":"arcaflow/contributing/typing/#lists-maps","title":"Lists, maps","text":"Lists and maps can have constraints on the minimum or maximum number of items in them (inclusive).
"},{"location":"arcaflow/contributing/typing/#objects","title":"Objects","text":"Object fields can have several constraints:
required_if
has a list of other fields that, if set, make the current field required. required_if_not
has a list of other fields that, if none are set, make the current field required. conflicts
has a list of other fields that cannot be set together with the current field.
"},{"location":"arcaflow/contributing/typing/#oneof","title":"OneOf","text":"When you need to create a list of multiple object types, or simply have an either-or choice between two object types, you can use the OneOf type. This field uses an already existing field of the underlying objects, or adds an extra field to the schema to distinguish between the different types. Translated to JSON, you might see something like this:
{\n \"_type\": \"Greeter\",\n \"message\": \"Hello world!\"\n}\n
"},{"location":"arcaflow/contributing/typing/#scopes-and-refs","title":"Scopes and refs","text":"Objects, on their own, cannot create circular references. It is not possible to create two objects that refer to each other. That\u2019s where scopes and refs come into play. Scopes hold a list of objects, identified by an ID. Refs inside the scope (for example, in an object property) can refer to these IDs. Every scope has a root object, which will be used to provide its \u201cobject-like\u201d features, such as a list of fields.
For example:
objects:\n my_root_object:\n id: my_root_object\n properties:\n ...\nroot: my_root_object\n
Multiple scopes can be nested into each other. The ref always refers to the closest scope up the tree. Multiple scopes can be used when combining objects from several sources (e.g. several plugins) into one schema to avoid conflicting ID assignments.
"},{"location":"arcaflow/contributing/typing/#any","title":"Any","text":"Any accepts any primitive type (string, int, float, bool, map, list) but no patterns, objects, etc. This type is severely limited in its ability to validate data and should only be used in exceptional cases when there is no other way to describe a schema.
"},{"location":"arcaflow/contributing/typing/#metadata","title":"Metadata","text":"Object fields can also declare metadata that will help with creating user interfaces for the object. These fields are:
- name: A user-readable name for the field.
- description: A user-readable description for the field. It may contain newlines, but no other formatting is allowed.
- icon: SVG icon
"},{"location":"arcaflow/contributing/typing/#intent-inference","title":"Intent inference","text":"For display purposes, the type system is designed so that it can infer the intent of the data. We wish to communicate the following intents:
- Graphs are x-y values of timestamps mapped to one or more values.
- Log lines are timestamps associated with text.
- Events are timestamps associated with other structured data.
We explicitly document the following inference rules, which will probably change in the future.
- A map with keys of timestamps and values of integers or floats is rendered as a graph.
- A map with keys of timestamps and values of objects consisting only of integers and floats is rendered as a graph.
- A map with keys of timestamps and values of strings is considered a log line.
- A map with keys of timestamps and objects that don\u2019t match the rules above are considered an event.
- A map with keys of short strings and integer or float values is considered a pie chart.
- A list of objects consisting of a single timestamp and otherwise only integers and floats is rendered as a graph.
- A list of objects with a single timestamp and a single string are considered a log line.
- A list of objects with a single short string and a single integer or float is considered a pie chart.
- A list of objects consisting of no more than one timestamp and multiple other fields not matching the rules above is considered an event.
- If an object has a field called \u201ctitle\u201d, \u201cname\u201d, or \u201clabel\u201d, it will be used as a label for the current data set in a chart, or as a title for the wrapping box for the user interface elements.
"},{"location":"arcaflow/contributing/typing/#reference-manual","title":"Reference Manual","text":"This section explains how a scope object looks like. The plugin protocol contains a few more types that are used when communicating a schema.
Type: scope
Root object: Scope Properties objects (map[string, reference[Object]]
) Name: Objects Description: A set of referencable objects. These objects may contain references themselves. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Object]
Referenced object: Object (see in the Objects section below) root (string
) Name: Root object Description: ID of the root object of the scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Objects AnySchema (object
) Type: object
Properties None
BoolSchema (object
) Type: object
Properties None
Display (object
) Type: object
Properties description (string
) Name: Description Description: Description for this item if needed. Required: No Minimum: 1 Examples \"Please select the fruit you would like.\"\n
icon (string
) Name: Icon Description: SVG icon for this item. Must have the declared size of 64x64, must not include additional namespaces, and must not reference external resources. Required: No Minimum: 1 Examples \"<svg ...></svg>\"\n
name (string
) Name: Name Description: Short text serving as a name or title for this item. Required: No Minimum: 1 Examples \"Fruit\"\n
Float (object
) Type: object
Properties max (float
) Name: Maximum Description: Maximum value for this float (inclusive). Required: No Examples 16.0\n
min (float
) Name: Minimum Description: Minimum value for this float (inclusive). Required: No Examples 5.0\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
Int (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum value for this int (inclusive). Required: No Minimum: 0 Examples 5\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
IntEnum (object
) Type: object
Properties units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
values (map[int, reference[Display]]
) Name: Values Description: Possible values for this field. Required: Yes | Minimum items: | 1 |\n
Key type Type: int
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\"1024\": {\"name\": \"kB\"}, \"1048576\": {\"name\": \"MB\"}}\n
List (object
) Type: object
Properties items (one of[string]
) Name: Items Description: ReflectedType definition for items in this list. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
Map (object
) Type: object
Properties keys (one of[string]
) Name: Keys Description: ReflectedType definition for keys in this map. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
values (one of[string]
) Name: Values Description: ReflectedType definition for values in this map. Required: No Object (object
) Type: object
Properties id (string
) Name: ID Description: Unique identifier for this object within the current scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
properties (map[string, reference[Property]]
) Name: Properties Description: Properties of this object. Required: Yes Key type Type: string
Minimum: 1 Value type Type: reference[Property]
Referenced object: Property (see in the Objects section below) OneOfIntSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[int, one of[string]]
) Name: Types Required: No Key type Type: int
Value type Type: one of[string]
OneOfStringSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[string, one of[string]]
) Name: Types Required: No Key type Type: string
Value type Type: one of[string]
Pattern (object
) Type: object
Properties None
Property (object
) Type: object
Properties conflicts (list[string]
) Name: Conflicts Description: The current property cannot be set if any of the listed properties are set. Required: No List Items Type: string
default (string
) Name: Default Description: Default value for this property in JSON encoding. The value must be unserializable by the type specified in the type field. Required: No display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) examples (list[string]
) Name: Examples Description: Example values for this property, encoded as JSON. Required: No List Items Type: string
required (bool
) Name: Required Description: When set to true, the value for this field must be provided under all circumstances. Required: No Defaulttrue\n
required_if (list[string]
) Name: Required if Description: Sets the current property to required if any of the properties in this list are set. Required: No List Items Type: string
required_if_not (list[string]
) Name: Required if not Description: Sets the current property to be required if none of the properties in this list are set. Required: No List Items Type: string
type (one of[string]
) Name: Type Description: Type definition for this field. Required: Yes Ref (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) id (string
) Name: ID Description: Referenced object ID. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Scope (object
) Type: object
Properties objects (map[string, reference[Object]]
) Name: Objects Description: A set of referencable objects. These objects may contain references themselves. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Object]
Referenced object: Object (see in the Objects section below) root (string
) Name: Root object Description: ID of the root object of the scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
String (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 16\n
min (int
) Name: Minimum Description: Minimum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 5\n
pattern (pattern
) Name: Pattern Description: Regular expression this string must match. Required: No Examples \"^[a-zA-Z]+$\"\n
StringEnum (object
) Type: object
Properties values (map[string, reference[Display]]
) Name: Values Description: Mapping where the left side of the map holds the possible value and the right side holds the display value for forms, etc. Required: Yes | Minimum items: | 1 |\n
Key type Type: string
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\n \"apple\": {\n \"name\": \"Apple\"\n },\n \"orange\": {\n \"name\": \"Orange\"\n }\n}\n
Unit (object
) Type: object
Properties name_long_plural (string
) Name: Name long (plural) Description: Longer name for this UnitDefinition in plural form. Required: Yes Examples \"bytes\",\"characters\"\n
name_long_singular (string
) Name: Name long (singular) Description: Longer name for this UnitDefinition in singular form. Required: Yes Examples \"byte\",\"character\"\n
name_short_plural (string
) Name: Name short (plural) Description: Shorter name for this UnitDefinition in plural form. Required: Yes Examples \"B\",\"chars\"\n
name_short_singular (string
) Name: Name short (singular) Description: Shorter name for this UnitDefinition in singular form. Required: Yes Examples \"B\",\"char\"\n
Units (object
) Type: object
Properties base_unit (reference[Unit]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: Yes Referenced object: Unit (see in the Objects section below) Examples {\n \"name_short_singular\": \"B\",\n \"name_short_plural\": \"B\",\n \"name_long_singular\": \"byte\",\n \"name_long_plural\": \"bytes\"\n}\n
multipliers (map[int, reference[Unit]]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: No Key type Type: int
Value type Type: reference[Unit]
Referenced object: Unit (see in the Objects section below) Examples {\n \"1024\": {\n \"name_short_singular\": \"kB\",\n \"name_short_plural\": \"kB\",\n \"name_long_singular\": \"kilobyte\",\n \"name_long_plural\": \"kilobytes\"\n },\n \"1048576\": {\n \"name_short_singular\": \"MB\",\n \"name_short_plural\": \"MB\",\n \"name_long_singular\": \"megabyte\",\n \"name_long_plural\": \"megabytes\"\n }\n}\n
"},{"location":"arcaflow/plugins/","title":"Creating Arcaflow plugins","text":"Arcaflow supports writing plugins in any language, and we provide pre-made libraries for Python and Go.
Plugins in Arcaflow run in containers, so you can use dependencies and libraries.
Writing plugins in Python Python is the easiest language to start writing plugins in, you simply need to write a few dataclasses and a function and that\u2019s already a working plugin.
Read more about Python plugins \u00bb
Writing plugins in Go Go is the programming language of the engine. Writing plugins in Go is more complicated than Python because you will need to provide both the struct
s and the Arcaflow schema. We recommend Go for plugins that interact with Kubernetes.
Read more about Go plugins \u00bb
Packaging plugins To use plugins with Arcaflow, you will need to package them into a container image. You can, of course, write your own Dockerfile
, but we provide a handy utility called Carpenter to automate the process.
Read more about packaging \u00bb
"},{"location":"arcaflow/plugins/packaging/","title":"Packaging Arcaflow plugins","text":"Arcaflow plugins are distributed using container images. Whatever programming language you are using, you will need to package it up into a container image and distribute it via a container registry.
"},{"location":"arcaflow/plugins/packaging/#the-manual-method","title":"The manual method","text":"Currently, we only support the manual method for non-Arcalot plugins. However, it\u2019s very simple. First, create a Dockerfile for your programming language:
PythonGo With Python, the Dockerfile heavily depends on which build tool you are using. Here we are demonstrating the usage using pip.
FROM python:alpine\n\n# Add the plugin contents\nADD . /plugin\n# Set the working directory\nWORKDIR /plugin\n\n# Install the dependencies. Customize this\n# to your Python package manager.\nRUN pip install -r requirements.txt\n\n# Set this to your .py file\nENTRYPOINT [\"/usr/local/bin/python3\", /plugin/plugin.py\"]\n# Make sure this stays empty!\nCMD []\n
For Go plugins we recommend a multi-stage build so the source code doesn\u2019t unnecessarily bloat the image. (Keep in mind, for some libraries you will need to include at least a LICENSE and possibly a NOTICE file in the image.)
FROM golang AS build\n# Add the plugin contents\nADD . /plugin\n# Set the working directory\nWORKDIR /plugin\n# Build your image\nENV CGO_ENABLED=0\nRUN go build -o plugin\n\n# Start from an empty image\nFROM scratch\n# Copy the built binary\nCOPY --from=build /plugin/plugin /plugin\n# Set the entry point\nENTRYPOINT [\"/plugin\"]\n# Make sure this stays empty!\nCMD []\n
That\u2019s it! Now you can run your build:
docker build -t example.com/your-namespace/your-plugin:latest .\ndocker push example.com/your-namespace/your-plugin:latest\n
"},{"location":"arcaflow/plugins/go/","title":"Creating plugins in Go","text":"In contrast to Python, Go doesn\u2019t contain enough language elements to infer the types and validation from Go types. Therefore, in order to use Go you both need to create the data structures (e.g. struct
) and write the schema by hand. Therefore, we recommend Python for writing plugins.
For writing Go plugins, you will need:
- Go version 1.18 or higher.
- The Go SDK for Arcaflow plugins installed (preferably via go mod).
- A container engine that can build images for packaging.
If you have these three, you can get started with your first plugin.
"},{"location":"arcaflow/plugins/go/first/","title":"Writing your first Go plugin","text":"In order to create a Go plugin, you will need to create a Go module project (go mod init
) and install the Arcaflow SDK using go get go.flow.arcalot.io/pluginsdk
.
Writing a Go plugin consists of the following 4 parts:
- The input data model
- The output data model
- The callable function
- The calling scaffold
"},{"location":"arcaflow/plugins/go/first/#the-input-data-model","title":"The input data model","text":"First, we define an input data model. This must be a struct.
type Input struct {\n Name string `json:\"name\"`\n}\n
Note
The Arcaflow serialization does not use the built-in Go JSON marshaling, so any additional tags like omitempty
, or yaml
tags are ignored.
In addition to the struct above, we must also define a schema for the input data structure:
// We define a separate scope, so we can add sub-objects later.\nvar inputSchema = schema.NewScopeSchema(\n // Struct-mapped object schemas are object definitions that are mapped to a specific struct (Input)\n schema.NewStructMappedObjectSchema[Input](\n // ID for the object:\n \"input\",\n // Properties of the object:\n map[string]*schema.PropertySchema{\n \"name\": schema.NewPropertySchema(\n // Type properties:\n schema.NewStringSchema(nil, nil, nil),\n // Display metadata:\n schema.NewDisplayValue(\n schema.PointerTo(\"Name\"),\n schema.PointerTo(\"Name of the person to greet.\"),\n nil,\n ),\n // Required:\n true,\n // Required if:\n []string{},\n // Required if not:\n []string{},\n // Conflicts:\n []string{},\n // Default value, JSON encoded:\n nil,\n //Examples:\n nil,\n )\n },\n ),\n)\n
"},{"location":"arcaflow/plugins/go/first/#the-output-data-model","title":"The output data model","text":"The output data model is similar to the input. First, we define our output struct:
type Output struct {\n Message string `json:\"message\"`\n}\n
Then, we have to describe the schema for this output similar to the input:
var outputSchema = schema.NewScopeSchema(\n schema.NewStructMappedObjectSchema[Output](\n \"output\",\n map[string]*schema.PropertySchema{\n \"message\": schema.NewPropertySchema(\n schema.NewStringSchema(nil, nil, nil),\n schema.NewDisplayValue(\n schema.PointerTo(\"Message\"),\n schema.PointerTo(\"The resulting message.\"),\n nil,\n ),\n true,\n nil,\n nil,\n nil,\n nil,\n nil,\n )\n },\n ),\n)\n
"},{"location":"arcaflow/plugins/go/first/#the-callable-function","title":"The callable function","text":"Now we can create a callable function. This function will always take one input and produce an output ID (e.g. \"success\"
) and an output data structure. This allows you to return one of multiple possible outputs.
func greet(input Input) (string, any) {\n return \"success\", Output{\n fmt.Sprintf(\"Hello, %s!\", input.Name), \n }\n}\n
Finally, we can incorporate this function into a step schema:
var greetSchema = schema.NewCallableSchema(\n schema.NewCallableStep[Input](\n // ID of the function:\n \"greet\",\n // Add the input schema:\n inputSchema,\n map[string]*schema.StepOutputSchema{\n // Define possible outputs:\n \"success\": schema.NewStepOutputSchema(\n // Add the output schema:\n outputSchema,\n schema.NewDisplayValue(\n schema.PointerTo(\"Success\"),\n schema.PointerTo(\"Successfully created message.\"),\n nil,\n ),\n false,\n ),\n },\n // Metadata for the function:\n schema.NewDisplayValue(\n schema.PointerTo(\"Greet\"),\n schema.PointerTo(\"Greets the user.\"),\n nil,\n ),\n // Reference the function\n greet,\n )\n)\n
"},{"location":"arcaflow/plugins/go/first/#the-calling-scaffold","title":"The calling scaffold","text":"Finally, we need to create our main function to run the plugin:
package main\n\nimport (\n \"go.flow.arcalot.io/pluginsdk/plugin\"\n)\n\nfunc main() {\n plugin.Run(greetSchema)\n}\n
"},{"location":"arcaflow/plugins/go/first/#running-the-plugin","title":"Running the plugin","text":"Go plugins currently cannot run as CLI tools, so you will have to use this plugin in conjunction with the Arcaflow Engine. However, you can dump the schema by running:
go run yourplugin.go --schema\n
"},{"location":"arcaflow/plugins/go/first/#next-steps","title":"Next steps","text":"Once you are finished with your first plugin, you should read the section about writing a schema.
"},{"location":"arcaflow/plugins/go/schema/","title":"Writing a schema in Go","text":"In contrast to Python, the Go SDK does not have the ability to infer the schema from the code of a plugin. The Go language does not have enough information to provide enough information.
Therefore, schemas in Go need to be written by hand. This document will explain the details and intricacies of writing a Go schema by hand.
"},{"location":"arcaflow/plugins/go/schema/#typed-vs-untyped-serialization","title":"Typed vs. untyped serialization","text":"Since Go is a strongly and statically typed language, there are two ways to serialize and unserialize a type.
The untyped serialization functions (Serialize
, Unserialize
) always result in an any
type (interface{}
for pre-1.18 code) and you will have to perform a type assertion to get the type you can actually work with.
The typed serialization functions (SerializeType
, UnserializeType
) result in a specific type, but cannot be used in lists, maps, etc. due to the lack of language features, such as covariance.
In practice, you will always use untyped functions when writing a plugin, typed functions are only useful for writing Arcaflow Engine code.
"},{"location":"arcaflow/plugins/go/schema/#strings","title":"Strings","text":"You can define a string by calling schema.NewStringSchema()
. It has 3 parameters:
- The minimum number of characters in the string. (
*int64
) - The maximum number of characters in the string. (
*int64
) - A regular expression the string must match. (
*regexp.Regexp
)
It will result in a *StringSchema
, which also complies with the schema.String
interface. It unserializes from a string, integer, float to a string and serializes back to a string.
Tip
You can easily convert a value to a pointer by using the schema.PointerTo()
function.
"},{"location":"arcaflow/plugins/go/schema/#patterns","title":"Patterns","text":"You can define a regular expression pattern by calling schema.NewPatternSchema()
. It has no parameters and will result in a *PatternSchema
, which also complies with the schema.Pattern
interface. It unserializes from a string to a *regexp.Regexp
and serializes back to a string.
"},{"location":"arcaflow/plugins/go/schema/#integer","title":"Integer","text":"Integers in Are always 64-bit signed integers. You can define an integer type with the schema.NewIntSchema()
function. It takes the following parameters:
- The minimum value for the integer. (
*int64
) - The maximum value for the integer. (
*int64
) - The units of the integer. (
*UnitsDefinition
, see Units)
When unserializing from a string, or another int or float type, the SDK will attempt to parse it as an integer. When serializing, the integer type will always be serialized as an integer.
"},{"location":"arcaflow/plugins/go/schema/#floating-point-numbers","title":"Floating point numbers","text":"Floating point numbers are always stored as 64-bit floating point numbers. You can define a float type with the schema.NewFloatSchema()
function. It takes the following parameters:
- The minimum value for the float. (
*float64
) - The maximum value for the float. (
*float64
) - The units of the float. (
*UnitsDefinition
, see Units)
When unserializing from a string, or another int or float type, the SDK will attempt to parse it as a float. When serializing, the float type will always be serialized as a float.
"},{"location":"arcaflow/plugins/go/schema/#booleans","title":"Booleans","text":"You can define a boolean by calling schema.NewBoolSchema()
. It has no parameters and will result in a *BoolSchema
, which also complies with the schema.Bool
interface.
It converts both integers and strings to boolean if possible. The following values are accepted as true
or false
, respectively:
1
yes
y
on
true
enable
enabled
0
no
n
off
false
disable
disabled
Boolean types will always serialize to bool
.
"},{"location":"arcaflow/plugins/go/schema/#enums","title":"Enums","text":"Go doesn\u2019t have any built-in enums, so Arcaflow supports int64
and string
-based enums. You can define an int enum by calling the schema.NewIntEnumSchema()
function. It takes the following parameters:
- A
map[int64]*DisplayValue
of values. The keys are the valid values in the enum. The values are display values, which can also be nil if no special display properties are desired. - The units of the enum. (
*UnitsDefinition
, see Units)
Strings can be defined by using the schema.NewStringEnumSchema()
function, which only takes the first parameter with string
keys.
Both functions return a *EnumSchema[string|int64]
, which also complies with the Enum[string|int64]
interface.
"},{"location":"arcaflow/plugins/go/schema/#lists","title":"Lists","text":"Lists come in two variants: typed and untyped. (See Typed vs. Untyped.) You can create an untyped list by calling schema.NewListSchema()
and a typed list by calling schema.NewTypedListSchema()
. Both have the following parameters:
- The type of item in the list. For untyped lists, this is a plain schema, for typed lists this must also be a typed schema.
- The minimum number of items in the list. (
*int64
) - The maximum number of items in the list. (
*int64
)
The result is a *ListSchema
for untyped lists, and a *TypedListSchema
for typed lists, which also satisfy their corresponding interfaces.
"},{"location":"arcaflow/plugins/go/schema/#maps","title":"Maps","text":"Maps, like lists, come in two variants: typed and untyped. (See Typed vs. Untyped.) You can create an untyped map by calling schema.NewMapSchema()
and a typed map by calling schema.NewTypedMapSchema()
. They both have the following parameters:
- The key type. This must be a schema of
string
, int
, or an enum thereof. - The value type. This can be any schema.
- The minimum number of items in the map. (
*int64
) - The maximum number of items in the map. (
*int64
)
The functions return a *schema.MapSchema
and *schema.TypedMapSchema
, respectively, which satisfy their corresponding interfaces.
"},{"location":"arcaflow/plugins/go/schema/#objects","title":"Objects","text":"Objects come in not two, but three variants: untyped, struct-mapped, and typed. (See Typed vs. Untyped.) Untyped objects unserialize to a map[string]any
, whereas struct-mapped objects are bound to a struct, but behave like untyped objects. Typed objects are bound to a struct and are typed. In plugins, you will always want to use struct-mapped object schemas.
You can create objects with the following functions:
schema.NewObjectSchema
for untyped objects. schema.NewStructMappedObjectSchema
for struct-mapped objects. schema.NewTypedObject
for typed objects.
They all have two parameters:
- A unique object identifier in the current scope. (See Scopes.)
- A map of string to PropertySchema objects describing the object properties.
"},{"location":"arcaflow/plugins/go/schema/#properties","title":"Properties","text":"Properties of objects are always untyped. You can create a property by calling schema.NewPropertySchema()
and it has the following parameters:
- The underlying type for the property.
- The display options for this property. (See Display values.)
- If the property is required. (
bool
) - The required-if fields. If any of these fields in the current object is set, the current property also becomes required. (
[]string
) - The required-if-not fields. If none of these fields are set in the current object, the current property becomes required. (
[]string
) - The fields the current field conflicts with. If any of these fields are set, the current field must not be set. (
[]string
) - The default value for the current property. (JSON-serialized
*string
) - Examples for the current property. (JSON-serialized
[]string
)
"},{"location":"arcaflow/plugins/go/schema/#scopes","title":"Scopes","text":"Sometimes, objects need to have circular references to each other. That\u2019s where scopes help. Scopes behave like objects, but act as a container for Refs. They contain a root object and additional objects that can be referenced by ID.
You can create a scope by calling schema.NewScopeSchema()
. It takes the following parameters:
- The root object.
- A list of additional objects that can be referenced by ID.
Warning
When using scopes, you must call ApplyScope
on the outermost scope once you have constructed your type tree, otherwise references won\u2019t work.
"},{"location":"arcaflow/plugins/go/schema/#refs","title":"Refs","text":"Refs are references to objects in the current scope. You can create a ref by calling schema.NewRefSchema()
. It takes two parameters:
- The ID of the object referenced.
- The display properties of this reference. (See Display values.)
"},{"location":"arcaflow/plugins/go/schema/#one-of","title":"One-of","text":"Sometimes, a field must be able to hold more than one type of item. That\u2019s where one-of types come into play. They behave like objects, but have a special field called the discriminator which differentiates between the different possible types. This discriminator field can either be an integer or a string.
You can use schema.NewOneOfIntSchema()
to create an integer-based one-of type and schema.NewOneOfStringSchema()
to create a string-based one. They both accept two parameters:
- A
map[int64|string]Object
, which holds the discriminator values and their corresponding objects (these can be refs or scopes too). - A
string
holding the name of the discriminator field.
The objects in the map are allowed to skip the discriminator field, but if they use it, it must have the same type as listed here.
"},{"location":"arcaflow/plugins/go/schema/#any","title":"Any","text":"The \u201cany\u201d type allows any primitive type to pass through. However, this comes with severe limitations and the data cannot be validated, so its use is discouraged. You can create an AnySchema
by calling schema.NewAnySchema()
. This function has no parameters.
"},{"location":"arcaflow/plugins/go/schema/#display-values","title":"Display values","text":"Several types, for example properties, accept a display value. This is a value designed to be rendered as a form field. It has three parameters:
- A short, human-readable name.
- A longer, possibly multi-line description.
- An embedded SVG icon. This icon should be 64x64 pixels and not contain any external references (e.g. CSS.)
Display types are always optional (can be nil
) and you can create one by calling schema.NewDisplayValue()
"},{"location":"arcaflow/plugins/go/schema/#units","title":"Units","text":"Units make it easier to parse and display numeric values. For example, if you have an integer representing nanoseconds, you may want to parse strings like 5m30s
. This is similar to the duration type in Go, but with the capabilities of defining your own units.
Units have two parameters: the base type and multipliers. You can define a unit type by calling schema.NewUnits()
and provide the base unit and multipliers by calling schema.NewUnit()
.
var u = schema.NewUnits(\n // Base unit:\n NewUnit(\n // Short name, singular\n \"B\",\n // Short name, plural\n \"B\",\n // Long name, singular\n \"byte\",\n // Long name, plural\n \"bytes\",\n ),\n // Multipliers\n map[int64]*UnitDefinition{\n 1024: NewUnit(\n \"kB\",\n \"kB\",\n \"kilobyte\",\n \"kilobytes\",\n ),\n //...\n },\n)\n
You can use the built-in schema.UnitBytes
, schema.UnitDurationNanoseconds
, and schema.UnitDurationSeconds
units for your plugins.
"},{"location":"arcaflow/plugins/python/","title":"Creating plugins with Python","text":"If you want to create an Arcaflow plugin in Python, you will need three things:
- Python 3.9+ (PyPy is supported)
- The Python SDK for Arcaflow plugins
- A container engine that can build images for packaging.
If you have these three, you can get started with your first plugin.
"},{"location":"arcaflow/plugins/python/data-model/","title":"Creating a Python data model","text":"Every plugin needs a schema to represent its expected inputs and outputs in a machine-readable format. The schema strong typing is a core design element of Arcaflow, enabling us to build portable workflows that compartmentalize failure conditions and avoid data errors.
When creating a data model for Arcaflow plugins in Python, everything starts with dataclasses. They allow Arcaflow to get information about the data types of individual fields in your class:
plugin.pyimport dataclasses\n\n\n@dataclasses.dataclass\nclass MyDataModel:\n some_field: str\n other_field: int\n
However, Arcaflow doesn\u2019t support all Python data types. You pick from the following list:
str
int
float
bool
- Enums
re.Pattern
typing.List[othertype]
typing.Dict[keytype, valuetype]
typing.Union[onedataclass, anotherdataclass]
- Dataclasses
typing.Any
You can read more about the individual types in the data types section
"},{"location":"arcaflow/plugins/python/data-model/#optional-parameters","title":"Optional parameters","text":"You can also declare any parameter as optional, like this:
plugin.py@dataclasses.dataclass\nclass MyClass:\n param: typing.Optional[int] = None\n
Note that adding typing.Optional
is not enough, you must specify the default value.
"},{"location":"arcaflow/plugins/python/data-model/#annotations-for-validation-and-metadata","title":"Annotations for validation and metadata","text":"You can specify desired validations for each field like this:
plugin.py@dataclasses.dataclass\nclass MyClass:\n param: typing.Annotated[int, schema.name(\"Param\")]\n
Tip
Annotated objects are preferred as a best practice for a documented schema, and are expected for any officially-supported community plugins.
You can use the following annotations to add metadata to your fields:
schema.id
adds a serialized field name for the current field (e.g. one containing dashes, which is not valid in Python) schema.name
adds a human-readable name to the parameter. This can be used to present a form field. schema.description
adds a long-form description to the field. schema.example
adds an example value to the field. You can repeat this annotation multiple times. The example must be provided as primitive types (no dataclasses).
You can also add validations to the fields. The following annotations are valid for all data types:
schema.required_if
specifies a field that causes the current field to be required. If the other field is empty, the current field is not required. You can repeat this annotation multiple times. (Make sure to use the optional annotation above.) schema.required_if_not
specifies a field that, if not filled, causes the current field to be required. You can repeat this annotation multiple times.(Make sure to use the optional annotation above.) schema.conflicts
specifies a field that cannot be used together with the current field. You can repeat this annotation multiple times. (Make sure to use the optional annotation above.)
Additionally, some data types have their own validations and metadata, such as schema.min
, schema.max
, schema.pattern
, or schema.units
.
Note
When combining typing.Annotated
with typing.Optional
, the default value is assigned to the Annotated
object, not to the Optional
object.
plugin.py@dataclasses.dataclass\nclass MyClass:\n param: typing.Annotated[\n typing.Optional[int],\n schema.name(\"Param\")\n ] = None\n
"},{"location":"arcaflow/plugins/python/data-model/#data-types","title":"Data types","text":""},{"location":"arcaflow/plugins/python/data-model/#strings","title":"Strings","text":"Strings are, as the name suggests, strings of human-readable characters. You can specify them in your dataclass like this:
some_field: str\n
Additionally, you can apply the following validations:
schema.min()
specifies the minimum length of the string if the field is set. schema.max()
specifies the maximum length of the string if the field is set. schema.pattern()
specifies the regular expression the string must match if the field is set.
"},{"location":"arcaflow/plugins/python/data-model/#integers","title":"Integers","text":"Integers are 64-bit signed whole numbers. You can specify them in your dataclass like this:
some_field: int\n
Additionally, you can apply the following validations and metadata:
schema.min()
specifies the minimum number if the field is set. schema.max()
specifies the maximum number if the field is set. schema.units()
specifies the units for this field (e.g. bytes). See Units.
"},{"location":"arcaflow/plugins/python/data-model/#floating-point-numbers","title":"Floating point numbers","text":"Floating point numbers are 64-bit signed fractions. You can specify them in your dataclass like this:
some_field: float\n
Warning
Floating point numbers are inaccurate! Make sure to transmit numbers requiring accuracy as integers!
Additionally, you can apply the following validations and metadata:
schema.min()
specifies the minimum number if the field is set. schema.max()
specifies the maximum number if the field is set. schema.units()
specifies the units for this field (e.g. bytes). See Units.
"},{"location":"arcaflow/plugins/python/data-model/#booleans","title":"Booleans","text":"Booleans are True
or False
values. You can specify them in your dataclass like this:
some_field: bool\n
Booleans have no additional validations or metadata.
"},{"location":"arcaflow/plugins/python/data-model/#enums","title":"Enums","text":"Enums, short for enumerations, are used to define a set of named values as unique constants. They provide a way to represent a fixed number of possible values for a variable, parameter, or property. In Python, an enum is declared as a class, but doesn\u2019t behave as a normal class. Instead, the \u201cattributes\u201d of the class act as independent \u201cmember\u201d or \u201cenumeration member\u201d objects, each of which has a name and a constant value.
By using enums, you can give meaningful names to distinct values, making the code more self-explanatory and providing a convenient way to work with sets of related constants.
In an Arcaflow schema, an Enum type provides a list of valid values for a field. The Enum must define a set of members with unique values, all of which are either strings or integers.
You can specify an enum with string values like this:
import enum\n\n\nclass MyEnum(enum.Enum):\n Value1 = \"value 1\"\n Value2 = \"value 2\"\n\nmy_field: MyEnum\n
The MyEnum class above defines two members, Value1 and Value2. Each member is associated with a constant value, in this case, the strings \u201cvalue 1\u201d and \u201cvalue 2\u201d respectively. An input value of \u201cvalue 1\u201d will result in the plugin seeing a value for my_field
of MyEnum.Value1. You can specify an Enum class with integer values like this:
import enum\n\nclass MyEnum(enum.Enum):\n Value1 = 1\n Value2 = 2\n\nmy_field: MyEnum\n
The my_field
variable is a variable of type MyEnum. It can store one of the defined enumeration members (Value1 or Value2). An input value of 1 in this case will result in the plugin receiving a value for my_field
of MyEnum.Value1.
value = MyEnum.Value1\n
In the above example, the Value1 member of MyEnum is accessed and assigned to the variable value. Note
Enumeration members are \u201csingleton\u201d objects which have a single instance. In Python, you should compare enumeration members using is
rather than ==
(for example, variable is MyEnum.Value1
). The values of an Enum used in an Arcaflow schema must have values of string or integer data type.
Tip
Enums aren\u2019t dataclasses, but can be used as the type of dataclass attributes.
Warning
Do not mix integers and strings in the same enum! The values for each Enum type must all be strings, or all integers.
"},{"location":"arcaflow/plugins/python/data-model/#patterns","title":"Patterns","text":"When you need to hold regular expressions, you can use a pattern field. This is tied to the Python regular expressions library. You can specify a pattern field like this:
import re\n\nmy_field: re.Pattern\n
Pattern fields have no additional validations or metadata.
Note
If you are looking for a way to do pattern/regex matching for a string you will need to use the schema.pattern() validation which specifies the regular expression, to which the string must match.
The below example declares that the first_name variable must only have uppercase and lowercase alphabets.
plugin.py@dataclasses.dataclass\nclass MyClass:\n first_name: typing.Annotated[\n str,\n schema.min(2),\n schema.pattern(re.compile(\"^[a-zA-Z]+$\")),\n schema.example(\"Arca\"),\n schema.name(\"First name\")\n ]\n
"},{"location":"arcaflow/plugins/python/data-model/#lists","title":"Lists","text":"When you want to make a list in Arcaflow, you always need to specify its contents. You can do that like this:
my_field: typing.List[str]\n
Lists can have the following validations:
schema.min()
specifies the minimum number of items in the list. schema.max()
specifies the maximum number of items in the list.
Tip
Items in lists can also be annotated with validations.
"},{"location":"arcaflow/plugins/python/data-model/#dicts","title":"Dicts","text":"Dicts (maps in Arcaflow) are key-value pairs. You need to specify both the key and the value type. You can do that as follows:
my_field: typing.Dict[str, str]\n
Lists can have the following validations:
schema.min()
specifies the minimum number of items in the list. schema.max()
specifies the maximum number of items in the list.
Tip
Items in dicts can also be annotated with validations.
"},{"location":"arcaflow/plugins/python/data-model/#union-types","title":"Union types","text":"Union types (one-of in Arcaflow) allow you to specify two or more possible objects (dataclasses) that can be in a specific place. The only requirement is that there must be a common field (discriminator) and each dataclass must have a unique value for this field. If you do not add this field to your dataclasses, it will be added automatically for you.
For example:
import typing\nimport dataclasses\n\n\n@dataclasses.dataclass\nclass FullName:\n first_name: str\n last_name: str\n\n\n@dataclasses.dataclass\nclass Nickname:\n nickname: str\n\n\nname: typing.Annotated[\n typing.Union[\n typing.Annotated[FullName, schema.discriminator_value(\"full\")],\n typing.Annotated[Nickname, schema.discriminator_value(\"nick\")]\n ], schema.discriminator(\"name_type\")]\n
Tip
The schema.discriminator
and schema.discriminator_value
annotations are optional. If you do not specify them, a discriminator will be generated for you.
"},{"location":"arcaflow/plugins/python/data-model/#any-types","title":"Any types","text":"Any types allow you to pass through any primitive data (no dataclasses). However, this comes with severe limitations as far as validation and use in workflows is concerned, so this type should only be used in limited cases. For example, if you would like to create a plugin that inserts data into an ElasticSearch database the \u201cany\u201d type would be appropriate here.
You can define an \u201cany\u201d type like this:
my_data: typing.Any\n
"},{"location":"arcaflow/plugins/python/data-model/#units","title":"Units","text":"Integers and floats can have unit metadata associated with them. For example, a field may contain a unit description like this:
time: typing.Annotated[int, schema.units(schema.UNIT_TIME)]\n
In this case, a string like 5m30s
will automatically be parsed into nanoseconds. Integers will pass through without conversion. You can also define your own unit types. At minimum, you need to specify the base type (nanoseconds in this case), and you can specify multipliers:
my_units = schema.Units(\n schema.Unit(\n # Short, singular\n \"ns\",\n # Short, plural\n \"ns\",\n # Long, singular\n \"nanosecond\",\n # Long, plural\n \"nanoseconds\"\n ),\n {\n 1000: schema.Unit(\n \"ms\",\n \"ms\",\n \"microsecond\",\n \"microseconds\"\n ),\n # ...\n }\n)\n
You can then use this description in your schema.units
annotations. Additionally, you can also use it to convert an integer or float into its string representation with the my_units.format_short
or my_units.format_long
functions. If you need to parse a string yourself, you can use my_units.parse
.
"},{"location":"arcaflow/plugins/python/data-model/#built-in-units","title":"Built-In Units","text":"A number of unit types are built-in to the python SDK for convenience:
UNIT_BYTE
- Bytes and 2^10 multiples (kilo-, mega-, giga-, tera-, peta-) UNIT_TIME
- Nanoseconds and human-friendly multiples (microseconds, seconds, minutes, hours, days) UNIT_CHARACTER
- Character notations (char, chars, character, characters) UNIT_PERCENT
- Percentage notations (%, percent)
"},{"location":"arcaflow/plugins/python/embedding/","title":"Embedding your Python plugin","text":"Instead of using your plugin as a standalone tool or in conjunction with Arcaflow, you can also embed your plugin into your existing Python application. To do that you simply build a schema and then call the schema yourself. You can pass raw data as an input, and you\u2019ll get the benefit of schema validation.
myapp.pyfrom arcaflow_plugin_sdk import plugin\nimport my_arcaflow_plugin\n\n# Build your schema with the step functions passed\nschema = plugin.build_schema(my_arcaflow_plugin.hello_world)\n\n# Which step from the plugin we want to execute\nstep_id = \"hello_world\"\n\n# Input parameters. Note, these must be a dict, not a dataclass\nstep_params = {\n \"name\": \"Arca Lot\",\n}\n\n# Execute the step\noutput_id, output_data = schema(step_id, step_params)\n\n# Print which kind of result we have\npprint.pprint(output_id)\n\n# Print the result data\npprint.pprint(output_data)\n
However, the example above requires you to provide the data as a dict
, not a dataclass
, and it will also return a dict
as an output object. Sometimes, you may want to use a partial approach, where you only use part of the SDK. In this case, you can change your code to run any of the following functions, in order:
serialization.load_from_file()
to load a YAML or JSON file into a dict yourschema.unserialize_input()
to turn a dict
into a dataclass
needed for your steps yourschema.call_step()
to run a step with the unserialized dataclass
yourschema.serialize_output()
to turn the output dataclass
into a dict
"},{"location":"arcaflow/plugins/python/faq/","title":"Python SDK FAQ","text":""},{"location":"arcaflow/plugins/python/faq/#how-can-i-add-a-field-with-dashes-such-as-my-field","title":"How can I add a field with dashes, such as my-field
?","text":"Dataclasses don\u2019t support dashes in parameters. You can work around this by defining the id
annotation:
@dataclasses.dataclass\nclass MyData:\n my_field: typing.Annotated[\n str,\n schema.id(\"my-field\"),\n ]\n
"},{"location":"arcaflow/plugins/python/faq/#how-can-i-write-a-dataclass-from-a-schema-to-a-yaml-or-json-file","title":"How can I write a dataclass from a schema to a YAML or JSON file?","text":"You can extend Python\u2019s JSON encoder to support dataclasses. If that doesn\u2019t suit your needs, you can use this SDK to convert the dataclasses to their basic representations and then write that to your JSON or YAML file. First, add this outside of your step:
my_object_schema = plugin.build_object_schema(MyDataclass)\n
Inside your step function you can then dump the data from your input
def my_step(params: MyParams):\n yaml_contents = yaml.dump(my_object_schema.serialize(params.some_param))\n
"},{"location":"arcaflow/plugins/python/faq/#how-can-i-easily-load-a-list-from-a-yaml-or-json-into-a-list-of-dataclasses","title":"How can I easily load a list from a YAML or JSON into a list of dataclasses?","text":"This requires a bit of trickery. First, we build a schema from the dataclass representing the row or entry in the list:
my_row_schema = plugin.build_object_schema(MyRow)\n
Then you can create a list schema:
my_list_schema = schema.ListType(my_row_schema)\n
You can now unserialize a list obtained from the YAML or JSON file:
my_data = my_list_schema.unserialize(json.loads(...))\n
"},{"location":"arcaflow/plugins/python/first/","title":"Writing your first Python plugin","text":"In this guide you will learn how to write a basic \u201cHello World\u201d plugin for Arcaflow and then run it without the engine as a standalone tool. In order to proceed this tutorial, you will need to install Python version 3.9 or higher on your machine. The tutorial will make use of the Arcaflow Python SDK to provide the required functionality.
"},{"location":"arcaflow/plugins/python/first/#step-1-setting-up-your-environment","title":"Step 1: Setting up your environment","text":"If you have Python installed, you will need to set up your environment. You can use any dependency manager you like, but here are three methods to get you started quickly.
Official plugins
If you wish to contribute an official Arcaflow plugin on GitHub, please use Poetry. For simplicity, we only accept Poetry plugins.
From the template repositoryUsing pipUsing Poetry - Clone or download the template repository
- Figure out what the right command to call your Python version is:
python3.10 --version\npython3.9 --version\npython3 --version\npython --version\n
Make sure you have at least Python 3.9. - Create a virtualenv in your project directory using the following command, replacing your Python call:
python -m venv venv\n
- Activate the venv:
source venv/bin/activate\n
- Install the dependencies:
pip install -r requirements.txt\n
- Create an empty folder.
- Create a
requirements.txt
with the following content: arcaflow-plugin-sdk\n
- Figure out what the right command to call your Python version is:
python3.10 --version\npython3.9 --version\npython3 --version\npython --version\n
Make sure you have at least Python 3.9. - Create a virtualenv in your project directory using the following command, replacing your Python call:
python -m venv venv\n
- Activate the venv:
source venv/bin/activate\n
- Install the dependencies:
pip install -r requirements.txt\n
- Copy the example plugin, example config and the [tests]https://github.com/arcalot/arcaflow-plugin-template-python/blob/main/tests/test_example_plugin.py) to your directory.
- Assuming you have Poetry installed, run the following command:
poetry new your-plugin\n
Then change the current directory to your-plugin
. - Figure out what the right command to call your Python version is:
which python3.10\nwhich python3.9\nwhich python3\nwhich python\n
Make sure you have at least Python 3.9. - Set Poetry to Python 3.9:
poetry env use /path/to/your/python3.9\n
- Check that your
pyproject.toml
file has the following lines: [tool.poetry.dependencies]\npython = \"^3.9\"\n
- Add the SDK as a dependency:
poetry add arcaflow-plugin-sdk\n
- Copy the example plugin, example config and the [tests]https://github.com/arcalot/arcaflow-plugin-template-python/blob/main/tests/test_example_plugin.py) to your directory.
- Activate the venv:
poetry shell\n
Now you are ready to start hacking away at your plugin! You can open the example_plugin.py
file and follow along, or you can create a new Python file and write the code.
"},{"location":"arcaflow/plugins/python/first/#step-2-creating-an-input-and-output-data-model","title":"Step 2: Creating an input and output data model","text":"Plugins in Arcaflow must explain how they want their input data and what kind of output they produce. Let\u2019s start with the input data model. In our case, we want to ask the user for a name. Normally, you would write this in Python:
plugin.pydef hello_world(name):\n return f\"Hello, {name}\"\n
However, that\u2019s not how the Arcaflow SDK works. You must always specify the data type of any variable. Additionally, every function can only have one input, and it must be a dataclass.
So, let\u2019s change the code a little:
plugin.pyimport dataclasses\n\n\n@dataclasses.dataclass\nclass InputParams:\n name: str\n\ndef hello_world(params: InputParams):\n # ...\n
So far so good, but we are not done yet. The output also has special rules. One plugin function can have more than one possible output, so you need to say which output it is, and you need to also return a dataclass.
For example:
plugin.pyimport dataclasses\n\n\n@dataclasses.dataclass\nclass InputParams:\n name: str\n\n\n@dataclasses.dataclass\nclass SuccessOutput:\n message: str\n\n\ndef hello_world(params: InputParams):\n return \"success\", SuccessOutput(f\"Hello, {params.name}\")\n
Tip
If your plugin has a problem, you could create and return an ErrorOutput
instead. In the Arcaflow workflow you can then handle each output separately.
"},{"location":"arcaflow/plugins/python/first/#step-3-decorating-your-step-function","title":"Step 3: Decorating your step function","text":"Of course, Arcaflow doesn\u2019t know what to do with this code yet. You will need to add a decorator to the hello_world
function in order to give Arcaflow the necessary information:
plugin.pyfrom arcaflow_plugin_sdk import plugin\n\n\n@plugin.step(\n id=\"hello-world\",\n name=\"Hello world!\",\n description=\"Says hello :)\",\n outputs={\"success\": SuccessOutput},\n)\ndef hello_world(params: InputParams):\n # ...\n
Let\u2019s go through the parameters:
id
provides the step identifier. If your plugin provides more than one step function, you need to specify this in your workflow. name
provides the human-readable name of the plugin step. This will help render a user interface for the workflow. description
is a longer description for the function and may contain line breaks. outputs
specifies the possible outputs and the dataclasses associated with these outputs. This is important so Arcaflow knows what to expect.
Tip
If you want, you can specify the function return type like this, but Arcaflow won\u2019t use it:
def hello_world(params: InputParams) -> typing.Tuple[str, ...]:\n
Unfortunately, Python doesn\u2019t give us a good way to extract this information, so it\u2019s safe to skip."},{"location":"arcaflow/plugins/python/first/#step-4-running-the-plugin","title":"Step 4: Running the plugin","text":"There is one more piece missing to run a plugin: the calling code. Add the following to your file:
plugin.pyimport sys\nfrom arcaflow_plugin_sdk import plugin\n\n\nif __name__ == \"__main__\":\n sys.exit(\n plugin.run(\n plugin.build_schema(\n # List your step functions here:\n hello_world,\n )\n )\n )\n
Now your plugin is ready. You can package it up for a workflow, or you can run it as a standalone tool from the command line:
python example_plugin.py -f input-data.yaml\n
You will need to provide the input data in YAML format:
input-data.yamlname: Arca Lot\n
Tip
If your plugin provides more than one step function, you can specify the correct one to use with the -s
parameter.
Tip
To prevent output from breaking the functionality when attached to the Arcaflow Engine, the SDK hides any output your step function writes to the standard output or standard error. You can use the --debug
flag to show any output on the standard error in standalone mode.
Tip
You can generate a JSON schema file for your step input by running
python example_plugin.py --json-schema input >example.schema.json\n
If you are using the YAML plugin for VSCode, add the following line to the top of your input file for code completion:
# yaml-language-server: $schema=example.schema.json\n
"},{"location":"arcaflow/plugins/python/first/#next-steps","title":"Next steps","text":"In order to create an actually useful plugin, you will want to create a data model for your plugin. Once the data model is complete, you should look into packaging your plugin.
"},{"location":"arcaflow/plugins/python/official/","title":"Creating official Arcaflow plugins","text":"Official Arcaflow plugins have more stringent requirements than normal. This document describes how to create a plugin that conforms to those requirements.
"},{"location":"arcaflow/plugins/python/official/#development-environment","title":"Development environment","text":"Official Python plugins are standardized on Poetry and a Linux-based development environment.
"},{"location":"arcaflow/plugins/python/official/#installing-poetry","title":"Installing Poetry","text":"First, please ensure your python3
executable is at least version 3.9.
$ python3 --version\nPython 3.9.15\n
How to install Python RHEL, CentOS, FedoraUbuntu $ dnf -y install python3.9\n
$ apt-get -y install python3.9\n
Tip
If the python3
command doesn\u2019t work for you, but python3.9
does, you can alias the command:
$ alias python3=\"python3.9\"\n
Install Poetry using one of their supported methods for your environment.
Warning
Make sure to install Poetry into exactly one Python executable on your system. If something goes wrong with your package\u2019s Python virtual environment, you do not want to also spend time figuring out which Poetry executable is responsible for it.
Now, verify your Poetry version.
$ poetry --version\nPoetry (version 1.2.2)\n
"},{"location":"arcaflow/plugins/python/official/#setting-up-your-project","title":"Setting up your project","text":"Create your plugin project, plugin-project
, and change directory into the project root. You should see a directory structure similar to this with the following files.
$ poetry new plugin-project\nCreated package plugin_project in plugin-project\n\n$ tree plugin-project\nplugin-project\n\u251c\u2500\u2500 plugin_project\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 README.md\n\u2514\u2500\u2500 tests\n \u2514\u2500\u2500 __init__.py\n\n2 directories, 4 files\n\n$ cd plugin-project\n
Ensure python3
is at least 3.9.
$ python3 --version\nPython 3.9.15\n
Set Poetry to use your Python that is at least 3.9.
$ poetry env use $(which python3)\n
Check that your pyproject.toml
is using at least Python 3.9 by looking for the following line.
pyproject.toml[tool.poetry.dependencies]\npython = \"^3.9\"\n
Add the arcaflow plugin sdk for python as a software dependency for your Python project.
$ poetry add arcaflow-plugin-sdk-python\n
You should now have a poetry.lock
file in your project root. Poetry maintains the state of your pyproject.toml
, and its exact software dependencies as hashes in the poetry.lock
file.
"},{"location":"arcaflow/plugins/python/official/#building-a-container-image","title":"Building a container image","text":"To build an official plugin container image we use the carpenter workflow on GitHub Actions. This workflow calls the Arcaflow image builder to build the image and perform all validations necessary.
In order to successfully run the build, you should add the following files from the template repository:
Dockerfile
LICENSE
.flake8
Additionally, you need to add tests to your project, write a README.md
, and make sure that the code directory matches your project name.
"},{"location":"arcaflow/plugins/python/official/#publishing-on-pypi","title":"Publishing on PyPI","text":"Some plugins work well as libraries too. You can publish Arcaflow plugins on PyPI.
To push an official package to PyPI, please contact an Arcalot chair to create an API token on PyPI and set up a CI environment. For testing purposes you can use TestPyPI.
You can configure Poetry to use this API token by calling:
$ poetry config pypi-token.<any name> <PYPI API TOKEN>\n
Alternatively, you can also use environment variables:
$ export POETRY_PYPI_TOKEN_PYPI=my-token\n$ export POETRY_HTTP_BASIC_PYPI_USERNAME=<username>\n$ export POETRY_HTTP_BASIC_PYPI_PASSWORD=<password>\n
You can generate distribution archives by typing:
$ poetry build\n
You can then test publishing:
$ poetry publish --dry-run\n\nPublishing arcaflow-plugin-template-python (0.1.0) to PyPI\n- Uploading arcaflow_plugin_template_python-0.1.0-py3-none-any.whl 100%\n- Uploading arcaflow_plugin_template_python-0.1.0.tar.gz 100%\n
Remove the --dry-run
to actually publish or call poetry publish --build
to run it in one go.
"},{"location":"arcaflow/plugins/python/schema/","title":"Writing a Python plugin schema by hand","text":"If you want to skip the automatic schema generation described in previous chapters, you can also create a schema by hand.
Warning
This process is complicated, requires providing redundant information and should be avoided if at all possible. We recommend creating a data model using dataclasses, decorators and annotations.
We start by defining a schema:
from arcaflow_plugin_sdk import schema\nfrom typing import Dict\n\nsteps: Dict[str, schema.StepSchema]\n\ns = schema.Schema(\n steps,\n)\n
The steps
parameter here must be a dict, where the key is the step ID and the value is the step schema. So, let\u2019s create a step schema:
from arcaflow_plugin_sdk import schema\n\nstep_schema = schema.StepSchema(\n id = \"pod\",\n name = \"Pod scenario\",\n description = \"Kills pods\",\n input = input_schema,\n outputs = outputs,\n handler = my_handler_func\n)\n
Let\u2019s go in order:
- The
input
must be a schema of the type schema.ObjectType
. This describes the single parameter that will be passed to my_handler_func
. - The
outputs
describe a Dict[str, schema.ObjectType]
, where the key is the ID for the returned output type, while the value describes the output schema. - The
handler
function takes one parameter, the object described in input
and must return a tuple of a string and the output object. Here the ID uniquely identifies which output is intended, for example success
and error
, while the second parameter in the tuple must match the outputs
declaration.
That\u2019s it! Now all that\u2019s left is to define the ObjectType
and any sub-objects.
"},{"location":"arcaflow/plugins/python/schema/#objecttype","title":"ObjectType","text":"The ObjectType is intended as a backing type for dataclasses. For example:
t = schema.ObjectType(\n TestClass,\n {\n \"a\": schema.Field(\n type=schema.StringType(),\n required=True,\n ),\n \"b\": schema.Field(\n type=schema.IntType(),\n required=True,\n )\n }\n)\n
The fields support the following parameters:
type
: underlying type schema for the field (required) name
: name for the current field description
: description for the current field required
: marks the field as required required_if
: a list of other fields that, if filled, will also cause the current field to be required required_if_not
: a list of other fields that, if not set, will cause the current field to be required conflicts
: a list of other fields that cannot be set together with the current field
"},{"location":"arcaflow/plugins/python/schema/#scopetype-and-reftype","title":"ScopeType and RefType","text":"Sometimes it is necessary to create circular references. This is where the ScopeType
and the RefType
comes into play. Scopes contain a list of objects that can be referenced by their ID, but one object is special: the root object of the scope. The RefType, on the other hand, is there to reference objects in a scope.
Currently, the Python implementation passes the scope to the ref type directly, but the important rule is that ref types always reference their nearest scope up the tree. Do not create references that aim at scopes not directly above the ref!
For example:
@dataclasses.dataclass\nclass OneOfData1:\n a: str\n\n@dataclasses.dataclass\nclass OneOfData2:\n b: OneOfData1\n\nscope = schema.ScopeType(\n {\n \"OneOfData1\": schema.ObjectType(\n OneOfData1,\n {\n \"a\": schema.Field(\n schema.StringType()\n )\n }\n ),\n },\n # Root object of scopes\n \"OneOfData2\",\n)\n\nscope.objects[\"OneOfData2\"] = schema.ObjectType(\n OneOfData2,\n {\n \"b\": schema.Field(\n schema.RefType(\"OneOfData1\", scope)\n )\n }\n)\n
As you can see, this API is not easy to use and is likely to change in the future.
"},{"location":"arcaflow/plugins/python/schema/#oneoftype","title":"OneOfType","text":"The OneOfType allows you to create a type that is a combination of other ObjectTypes. When a value is deserialized, a special discriminator field is consulted to figure out which type is actually being sent.
This discriminator field may be present in the underlying type. If it is, the type must match the declaration in the AnyOfType.
For example:
@dataclasses.dataclass\nclass OneOfData1:\n type: str\n a: str\n\n@dataclasses.dataclass\nclass OneOfData2:\n b: int\n\nscope = schema.ScopeType(\n {\n \"OneOfData1\": schema.ObjectType(\n OneOfData1,\n {\n # Here the discriminator field is also present in the underlying type\n \"type\": schema.Field(\n schema.StringType(),\n ),\n \"a\": schema.Field(\n schema.StringType()\n )\n }\n ),\n \"OneOfData2\": schema.ObjectType(\n OneOfData2,\n {\n \"b\": schema.Field(\n schema.IntType()\n )\n }\n )\n },\n # Root object of scopes\n \"OneOfData1\",\n)\n\ns = schema.OneOfStringType(\n {\n # Option 1\n \"a\": schema.RefType(\n # The RefType resolves against the scope.\n \"OneOfData1\",\n scope\n ),\n # Option 2\n \"b\": schema.RefType(\n \"OneOfData2\",\n scope\n ),\n },\n # Pass the scope this type belongs do\n scope,\n # Discriminator field\n \"type\",\n)\n\nserialized_data = s.serialize(OneOfData1(\n \"a\",\n \"Hello world!\"\n))\npprint.pprint(serialized_data)\n
Note, that the OneOfTypes take all object-like elements, such as refs, objects, or scopes.
"},{"location":"arcaflow/plugins/python/schema/#stringtype","title":"StringType","text":"String types indicate that the underlying type is a string.
t = schema.StringType()\n
The string type supports the following parameters:
min_length
: minimum length for the string (inclusive) max_length
: maximum length for the string (inclusive) pattern
: regular expression the string must match
"},{"location":"arcaflow/plugins/python/schema/#patterntype","title":"PatternType","text":"The pattern type indicates that the field must contain a regular expression. It will be decoded as re.Pattern
.
t = schema.PatternType()\n
The pattern type has no parameters.
"},{"location":"arcaflow/plugins/python/schema/#inttype","title":"IntType","text":"The int type indicates that the underlying type is an integer.
t = schema.IntType()\n
The int type supports the following parameters:
min
: minimum value for the number (inclusive). max
: minimum value for the number (inclusive).
"},{"location":"arcaflow/plugins/python/schema/#floattype","title":"FloatType","text":"The float type indicates that the underlying type is a floating point number.
t = schema.FloatType()\n
The float type supports the following parameters:
min
: minimum value for the number (inclusive). max
: minimum value for the number (inclusive).
"},{"location":"arcaflow/plugins/python/schema/#booltype","title":"BoolType","text":"The bool type indicates that the underlying value is a boolean. When unserializing, this type also supports string and integer values of true
, yes
, on
, enable
, enabled
, 1
, false
, no
, off
, disable
, disabled
or 0
.
"},{"location":"arcaflow/plugins/python/schema/#enumtype","title":"EnumType","text":"The enum type creates a type from an existing enum:
class MyEnum(Enum):\n A = \"a\"\n B = \"b\"\n\nt = schema.EnumType(MyEnum)\n
The enum type has no further parameters.
"},{"location":"arcaflow/plugins/python/schema/#listtype","title":"ListType","text":"The list type describes a list of items. The item type must be described:
t = schema.ListType(\n schema.StringType()\n)\n
The list type supports the following extra parameters:
min
: The minimum number of items in the list (inclusive) max
: The maximum number of items in the list (inclusive)
"},{"location":"arcaflow/plugins/python/schema/#maptype","title":"MapType","text":"The map type describes a key-value type (dict). You must specify both the key and the value type:
t = schema.MapType(\n schema.StringType(),\n schema.StringType()\n)\n
The map type supports the following extra parameters:
min
: The minimum number of items in the map (inclusive) max
: The maximum number of items in the map (inclusive)
"},{"location":"arcaflow/plugins/python/schema/#anytype","title":"AnyType","text":"The \u201cany\u201d type allows any primitive type to pass through. However, this comes with severe limitations and the data cannot be validated, so its use is discouraged. You can create an AnyType
by simply doing this:
t = schema.AnyType()\n
"},{"location":"arcaflow/plugins/python/schema/#running-the-plugin","title":"Running the plugin","text":"If you create the schema by hand, you can add the following code to your plugin:
if __name__ == \"__main__\":\n sys.exit(plugin.run(your_schema))\n
You can then run your plugin as described in the writing your first plugin section.
"},{"location":"arcaflow/plugins/python/testing/","title":"Testing your Python plugin","text":"When writing your first plugin, you will probably want to test it manually. However, as development progresses, you should switch to automated testing. Automated testing makes sure your plugins don\u2019t break when you introduce changes.
This page describes the following test scenarios:
- Manual testing helps
- Serialization tests for your input and output to make sure your classes can be serialized for transport
- Functional tests that call your plugin and make sure it works correctly
"},{"location":"arcaflow/plugins/python/testing/#manual-testing","title":"Manual testing","text":"Manual testing is easy: prepare a test input file in YAML format, then run the plugin as a command line tool. For example, the hello world plugin would take this input:
name: Arca Lot\n
You could then run the example plugin:
python example_plugin -f my-input-file.yaml\n
The plugin will run and present you with the output.
Tip
If you have more than one step, don\u2019t forget to pass the -s step-id
parameter.
Tip
To prevent output from breaking the functionality when attached to the Arcaflow Engine, the SDK hides any output your step function writes to the standard output or standard error. You can use the --debug
flag to show any output on the standard error in standalone mode.
"},{"location":"arcaflow/plugins/python/testing/#writing-a-serialization-test","title":"Writing a serialization test","text":"You can use any test framework you like for your serialization test, we\u2019ll demonstrate with unittest as it is included directly in Python. The key to this test is to call plugin.test_object_serialization()
with an instance of your dataclass that you want to test:
class ExamplePluginTest(unittest.TestCase):\n def test_serialization(self):\n self.assertTrue(plugin.test_object_serialization(\n example_plugin.PodScenarioResults(\n [\n example_plugin.Pod(\n namespace=\"default\",\n name=\"nginx-asdf\"\n )\n ]\n )\n ))\n
Remember, you need to call this function with an instance containing actual data, not just the class name.
The test function will first serialize, then unserialize your data and check if it\u2019s the same. If you want to use a manually created schema, you can do so, too:
class ExamplePluginTest(unittest.TestCase):\n def test_serialization(self):\n plugin.test_object_serialization(\n example_plugin.PodScenarioResults(\n #...\n ),\n schema.ObjectType(\n #...\n )\n )\n
"},{"location":"arcaflow/plugins/python/testing/#functional-tests","title":"Functional tests","text":"Functional tests don\u2019t have anything special about them. You can directly call your code with your dataclasses as parameters, and check the return. This works best on auto-generated schemas with the @plugin.step
decorator. See below for manually created schemas.
class ExamplePluginTest(unittest.TestCase):\n def test_functional(self):\n input = example_plugin.PodScenarioParams()\n\n output_id, output_data = example_plugin.pod_scenario(input)\n\n # Check if the output is always an error, as it is the case for the example plugin.\n self.assertEqual(\"error\", output_id)\n self.assertEqual(\n output_data,\n example_plugin.PodScenarioError(\n \"Cannot kill pod .* in namespace .*, function not implemented\"\n )\n )\n
If you created your schema manually, the best way to write your tests is to include the schema in your test. This will automatically validate both the input and the output, making sure they conform to your schema. For example:
class ExamplePluginTest(unittest.TestCase):\n def test_functional(self):\n step_schema = schema.StepSchema(\n #...\n handler = example_plugin.pod_scenario,\n )\n input = example_plugin.PodScenarioParams()\n\n output_id, output_data = step_schema(input)\n\n # Check if the output is always an error, as it is the case for the example plugin.\n self.assertEqual(\"error\", output_id)\n self.assertEqual(\n output_data,\n example_plugin.PodScenarioError(\n \"Cannot kill pod .* in namespace .*, function not implemented\"\n )\n )\n
"},{"location":"arcaflow/running/","title":"Running Arcaflow","text":"Running Arcaflow is simple! You will need three things:
- A local container engine (e.g. Docker or Podman)
- The Arcaflow Engine
- A workflow file (see the example workflows)
Please start by setting up Arcaflow.
"},{"location":"arcaflow/running/running/","title":"Running Arcaflow","text":"Before you proceed, you will need to perform the following steps:
- Download and configure Arcaflow
- Create a YAML file with your workflow input data (e.g. `input.yaml)
Linux/MacOSWindows /path/to/arcaflow -input path/to/input.yaml\n
c:\\path\\to\\arcaflow.exe -input path/to/input.yaml\n
You can pass the following additional options to Arcaflow:
Option Description -config /path/to/config.yaml
Set an Arcaflow configuration file. (See the configuration guide.) -context /path/to/workflow/dir
Set a different workflow directory. (Defaults to the current directory.) -workflow workflow.yaml
Set a different workflow file. (Defaults to workflow.yaml
.)"},{"location":"arcaflow/running/running/#execution","title":"Execution","text":"Once you start Arcaflow, it will perform the following three phases:
- It will start all plugins using your local deployer (see the configuration guide), load their schemas, and then stop the plugins.
Note
The loading phase only reads the plugin schemas; it does not run any of the functional steps of the plugins.
- It will execute the workflow.
- Once the workflow execution is complete, it will output the resulting data.
Tip
You can redirect the standard output to capture the output data and still read the log messages on the standard error.
"},{"location":"arcaflow/running/setup/","title":"Setting up Arcaflow","text":"In order to use Arcaflow, you will need to download the Arcaflow Engine. You can simply unpack and run it, no need for installing it.
On Linux and macOS, you may need to run chmod +x
on the engine binary.
"},{"location":"arcaflow/running/setup/#configuration","title":"Configuration","text":"If you are using Docker as the local deployer (see below), you generally do not need to perform any extra configuration.
If you wish to customize Arcaflow, you can pass a YAML configuration file to Arcaflow with the -config your-arcaflow-config.yaml
parameter.
"},{"location":"arcaflow/running/setup/#local-deployer","title":"Local deployer","text":"The Arcaflow Engine needs a local container deployer to temporarily run plugins and read their schema. We recommend either Docker (default) or Podman for this purpose. You can use a Kubernetes cluster for this purpose too, but a local container engine is the better choice for performance reasons.
You can then change the deployer type like this:
config.yamldeployer:\n type: podman\n # Deployer-specific options \n
DockerPodmanKubernetes Docker is the default local deployer. You can configure it like this:
config.yamldeployer:\n type: docker\n connection:\n # Change this to point to a TCP-based Docker socket\n host: host-to-docker \n # Add a certificates here. This is usually needed in TCP mode.\n cacert: |\n Add your CA cert PEM here\n cert: |\n Add your client cert PEM here.\n key: |\n Add your client key PEM here.\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n network:\n # Add your network config here\n platform:\n # Add your platform config here\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Docker deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Connection (object
) Type: object
Properties cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify the Dockerd server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name for Dockerd. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z0-9./:_-]+$
Default\"npipe:////./pipe/docker_engine\"\n
Examples 'unix:///var/run/docker.sock'\n
\u201d
'npipe:////./pipe/docker_engine'\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (map[string, string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[A-Z0-9_]+$
Value type Type: string
Maximum: 32760 Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
network (reference[NetworkConfig]
) Name: Network configuration Description: Provides information about the container networking for the plugin. Required: No Referenced object: NetworkConfig (see in the Objects section below) platform (reference[PlatformConfig]
) Name: Platform configuration Description: Provides information about the container host platform for the plugin. Required: No Referenced object: PlatformConfig (see in the Objects section below) HostConfig (object
) Type: object
Properties CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) NetworkConfig (object
) Type: object
Properties None
PlatformConfig (object
) Type: object
Properties None
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
If you want to use Podman as your local deployer instead of Docker, you can do so like this:
config.yamldeployer:\n type: podman\n podman:\n # Change where Podman is. (You can use this to point to a shell script\n path: /path/to/your/podman\n # Change the network mode\n networkMode: host\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Podman deployer Type: scope
Root object: Config Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) Objects Config (object
) Type: object
Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (list[string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+=.+$
Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
HostConfig (object
) Type: object
Properties Binds (list[string]
) Name: Volume Bindings Description: Volumes Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+:.+$
CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) Podman (object
) Type: object
Properties cgroupNs (string
) Name: CGroup namespace Description: Provides the Cgroup Namespace settings for the container Required: No Must match pattern: ^host|ns:/proc/\\d+/ns/cgroup|container:.+|private$
containerName (string
) Name: Container Name Description: Provides name of the container Required: No Must match pattern: ^.*$
imageArchitecture (string
) Name: Podman image Architecture Description: Provides Podman Image Architecture Required: No Must match pattern: ^.*$
Default\"amd64\"\n
imageOS (string
) Name: Podman Image OS Description: Provides Podman Image Operating System Required: No Must match pattern: ^.*$
Default\"linux\"\n
networkMode (string
) Name: Network Mode Description: Provides network settings for the container Required: No Must match pattern: ^bridge:.*|host|none$
path (string
) Name: Podman path Description: Provides the path of podman executable Required: No Must match pattern: ^.*$
Default\"podman\"\n
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
Kubernetes can be used as the \u201clocal\u201d deployer, but this is typically not recommended for performance reasons. You can set up the Kubernetes deployer like this:
config.yamldeployer:\n type: kubernetes\n connection:\n host: localhost:6443\n cert: |\n Add your client cert in PEM format here.\n key: |\n Add your client key in PEM format here.\n cacert: |\n Add the server CA cert in PEM format here.\n
All options for the Kubernetes deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects AWSElasticBlockStoreVolumeSource (object
) Type: object
Properties None
AzureDiskVolumeSource (object
) Type: object
Properties None
AzureFileVolumeSource (object
) Type: object
Properties None
CSIVolumeSource (object
) Type: object
Properties None
CephFSVolumeSource (object
) Type: object
Properties None
CinderVolumeSource (object
) Type: object
Properties None
Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) ConfigMapVolumeSource (object
) Type: object
Properties None
Connection (object
) Type: object
Properties bearerToken (string
) Name: Bearer token Description: Bearer token to authenticate against the Kubernetes API with. Required: No burst (int
) Name: Burst Description: Burst value for query throttling. Required: No Minimum: 0 Default10\n
cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify Kubernetes server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name and port of the Kubernetes server Required: No Default\"kubernetes.default.svc\"\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
password (string
) Name: Password Description: Password for basic authentication. Required: No path (string
) Name: Path Description: Path to the API server. Required: No Default\"/api\"\n
qps (float
) Name: QPS Description: Queries Per Second allowed against the API. Required: No Minimum: 0 Units: queries Default5.0\n
serverName (string
) Name: TLS server name Description: Expected TLS server name to verify in the certificate. Required: No username (string
) Name: Username Description: Username for basic authentication. Required: No Container (object
) Type: object
Properties args (list[string]
) Name: Arguments Description: Arguments to the entypoint (command). Required: No List Items Type: string
command (list[string]
) Name: Command Description: Override container entry point. Not executed with a shell. Required: No Minimum items: 1 List Items Type: string
env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) image (string
) Name: Image Description: Container image to use for this container. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9_\\-:./]+$
imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 workingDir (string
) Name: Working directory Description: Override the container working directory. Required: No DownwardAPIVolumeSource (object
) Type: object
Properties None
EmptyDirVolumeSource (object
) Type: object
Properties medium (string
) Name: Medium Description: How to store the empty directory Required: No Minimum: 1 Must match pattern: ^(|Memory|HugePages|HugePages-.*)$
EnvFromSource (object
) Type: object
Properties configMapRef (object
) Name: Config map source Description: Populates the source from a config map. Required: No Properties name (string
) Name: Name Description: Name of the referenced config map. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the config map must be defined. Required: No prefix (string
) Name: Prefix Description: An optional identifier to prepend to each key in the ConfigMap. Required: No Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
secretRef (object
) Name: Secret source Description: Populates the source from a secret. Required: No Properties name (string
) Name: Name Description: Name of the referenced secret. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the secret must be defined. Required: No EphemeralVolumeSource (object
) Type: object
Properties None
FCVolumeSource (object
) Type: object
Properties None
FlexVolumeSource (object
) Type: object
Properties None
FlockerVolumeSource (object
) Type: object
Properties None
GCEPersistentDiskVolumeSource (object
) Type: object
Properties None
GlusterfsVolumeSource (object
) Type: object
Properties None
HostPathVolumeSource (object
) Type: object
Properties path (string
) Name: Path Description: Path to the directory on the host. Required: Yes Minimum: 1 Examples \"/srv/volume1\"\n
type (enum[string]
) Name: Type Description: Type of the host path. Required: No Values - `` Unset
BlockDevice
Block device CharDevice
Character device Directory
Directory DirectoryOrCreate
Create directory if not found File
File FileOrCreate
Create file if not found Socket
Socket
ISCSIVolumeSource (object
) Type: object
Properties None
NFSVolumeSource (object
) Type: object
Properties None
ObjectMeta (object
) Type: object
Properties annotations (map[string, string]
) Name: Annotations Description: Kubernetes annotations to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
generateName (string
) Name: Name prefix Description: Name prefix to generate pod names from. Required: No labels (map[string, string]
) Name: Labels Description: Kubernetes labels to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
name (string
) Name: Name Description: Pod name. Required: No namespace (string
) Name: Namespace Description: Kubernetes namespace to deploy in. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"default\"\n
PersistentVolumeClaimVolumeSource (object
) Type: object
Properties None
PhotonPersistentDiskVolumeSource (object
) Type: object
Properties None
Pod (object
) Type: object
Properties metadata (reference[ObjectMeta]
) Name: Metadata Description: Pod metadata. Required: No Referenced object: ObjectMeta (see in the Objects section below) spec (reference[PodSpec]
) Name: Specification Description: Pod specification. Required: No Referenced object: PodSpec (see in the Objects section below) PodSpec (object
) Type: object
Properties affinity (object
) Name: Affinity rules Description: Affinity rules. Required: No Properties podAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
podAntiAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
containers (list[reference[Container]]
) Name: Containers Description: A list of containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) initContainers (list[reference[Container]]
) Name: Init containers Description: A list of initialization containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) nodeSelector (map[string, string]
) Name: Labels Description: Node labels you want the target node to have. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
pluginContainer (object
) Name: Plugin container Description: The container to run the plugin in. Required: Yes Properties env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"arcaflow-plugin-container\"\n
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 volumes (list[reference[Volume]]
) Name: Volumes Description: A list of volumes that can be mounted by containers belonging to the pod. Required: No List Items Type: reference[Volume]
Referenced object: Volume (see in the Objects section below) PortworxVolumeSource (object
) Type: object
Properties None
ProjectedVolumeSource (object
) Type: object
Properties None
QuobyteVolumeSource (object
) Type: object
Properties None
RBDVolumeSource (object
) Type: object
Properties None
ScaleIOVolumeSource (object
) Type: object
Properties None
SecretVolumeSource (object
) Type: object
Properties None
StorageOSVolumeSource (object
) Type: object
Properties None
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
Volume (object
) Type: object
Properties awsElasticBlockStore (reference[AWSElasticBlockStoreVolumeSource]
) Name: AWS EBS Description: AWS Elastic Block Storage. Required: No Referenced object: AWSElasticBlockStoreVolumeSource (see in the Objects section below) azureDisk (reference[AzureDiskVolumeSource]
) Name: Azure Data Disk Description: Mount an Azure Data Disk as a volume. Required: No Referenced object: AzureDiskVolumeSource (see in the Objects section below) azureFile (reference[AzureFileVolumeSource]
) Name: Azure File Description: Mount an Azure File Service mount. Required: No Referenced object: AzureFileVolumeSource (see in the Objects section below) cephfs (reference[CephFSVolumeSource]
) Name: CephFS Description: Mount a CephFS volume. Required: No Referenced object: CephFSVolumeSource (see in the Objects section below) cinder (reference[CinderVolumeSource]
) Name: Cinder Description: Mount a cinder volume attached and mounted on the host machine. Required: No Referenced object: CinderVolumeSource (see in the Objects section below) configMap (reference[ConfigMapVolumeSource]
) Name: ConfigMap Description: Mount a ConfigMap as a volume. Required: No Referenced object: ConfigMapVolumeSource (see in the Objects section below) csi (reference[CSIVolumeSource]
) Name: CSI Volume Description: Mount a volume using a CSI driver. Required: No Referenced object: CSIVolumeSource (see in the Objects section below) downwardAPI (reference[DownwardAPIVolumeSource]
) Name: Downward API Description: Specify a volume that the pod should mount itself. Required: No Referenced object: DownwardAPIVolumeSource (see in the Objects section below) emptyDir (reference[EmptyDirVolumeSource]
) Name: Empty directory Description: Temporary empty directory. Required: No Referenced object: EmptyDirVolumeSource (see in the Objects section below) ephemeral (reference[EphemeralVolumeSource]
) Name: Ephemeral Description: Mount a volume that is handled by a cluster storage driver. Required: No Referenced object: EphemeralVolumeSource (see in the Objects section below) fc (reference[FCVolumeSource]
) Name: Fibre Channel Description: Mount a Fibre Channel volume that's attached to the host machine. Required: No Referenced object: FCVolumeSource (see in the Objects section below) flexVolume (reference[FlexVolumeSource]
) Name: Flex Description: Mount a generic volume provisioned/attached using an exec based plugin. Required: No Referenced object: FlexVolumeSource (see in the Objects section below) flocker (reference[FlockerVolumeSource]
) Name: Flocker Description: Mount a Flocker volume. Required: No Referenced object: FlockerVolumeSource (see in the Objects section below) gcePersistentDisk (reference[GCEPersistentDiskVolumeSource]
) Name: GCE disk Description: Google Cloud disk. Required: No Referenced object: GCEPersistentDiskVolumeSource (see in the Objects section below) glusterfs (reference[GlusterfsVolumeSource]
) Name: GlusterFS Description: Mount a Gluster volume. Required: No Referenced object: GlusterfsVolumeSource (see in the Objects section below) hostPath (reference[HostPathVolumeSource]
) Name: Host path Description: Mount volume from the host. Required: No Referenced object: HostPathVolumeSource (see in the Objects section below) iscsi (reference[ISCSIVolumeSource]
) Name: iSCSI Description: Mount an iSCSI volume. Required: No Referenced object: ISCSIVolumeSource (see in the Objects section below) name (string
) Name: Name Description: The name this volume can be referenced by. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
nfs (reference[NFSVolumeSource]
) Name: NFS Description: Mount an NFS share. Required: No Referenced object: NFSVolumeSource (see in the Objects section below) persistentVolumeClaim (reference[PersistentVolumeClaimVolumeSource]
) Name: Persistent Volume Claim Description: Mount a Persistent Volume Claim. Required: No Referenced object: PersistentVolumeClaimVolumeSource (see in the Objects section below) photonPersistentDisk (reference[PhotonPersistentDiskVolumeSource]
) Name: PhotonController persistent disk Description: Mount a PhotonController persistent disk as a volume. Required: No Referenced object: PhotonPersistentDiskVolumeSource (see in the Objects section below) portworxVolume (reference[PortworxVolumeSource]
) Name: Portworx Volume Description: Mount a Portworx volume. Required: No Referenced object: PortworxVolumeSource (see in the Objects section below) projected (reference[ProjectedVolumeSource]
) Name: Projected Description: Projected items for all in one resources secrets, configmaps, and downward API. Required: No Referenced object: ProjectedVolumeSource (see in the Objects section below) quobyte (reference[QuobyteVolumeSource]
) Name: quobyte Description: Mount Quobyte volume from the host. Required: No Referenced object: QuobyteVolumeSource (see in the Objects section below) rbd (reference[RBDVolumeSource]
) Name: Rados Block Device Description: Mount a Rados Block Device. Required: No Referenced object: RBDVolumeSource (see in the Objects section below) scaleIO (reference[ScaleIOVolumeSource]
) Name: ScaleIO Persistent Volume Description: Mount a ScaleIO persistent volume. Required: No Referenced object: ScaleIOVolumeSource (see in the Objects section below) secret (reference[SecretVolumeSource]
) Name: Secret Description: Mount a Kubernetes secret. Required: No Referenced object: SecretVolumeSource (see in the Objects section below) storageos (reference[StorageOSVolumeSource]
) Name: StorageOS Volume Description: Mount a StorageOS volume. Required: No Referenced object: StorageOSVolumeSource (see in the Objects section below) vsphereVolume (reference[VsphereVirtualDiskVolumeSource]
) Name: vSphere Virtual Disk Description: Mount a vSphere Virtual Disk as a volume. Required: No Referenced object: VsphereVirtualDiskVolumeSource (see in the Objects section below) VsphereVirtualDiskVolumeSource (object
) Type: object
Properties None
"},{"location":"arcaflow/running/setup/#logging","title":"Logging","text":"Logging is useful when you need more information about what is happening while you run a workload.
"},{"location":"arcaflow/running/setup/#basic-logging","title":"Basic logging","text":"Here is the syntax for setting the log level: config.yaml
log:\n level: info\n
Options for the level
are:
debug
: Extra verbosity useful to developers info
: General info warning
: Something went wrong, and you should know about it error
: Something failed. This inf o should help you figure out why
This sets which types of log output are shown or hidden. debug
shows everything, while error
shows the least, only showing error
output. Each output shows more, rather than just its type, so debug
, info
, and warning
still show error
output.
"},{"location":"arcaflow/running/setup/#step-logging","title":"Step logging","text":"Step logging is useful for getting output from failed steps, or general debugging. It is not recommended that you rely on this long term, as there may be better methods of debugging failed workflows.
To make the workflow output just error
level logs when a step fails, set it as shown: config.yaml
logged_outputs:\n error:\n level: error\n
Tip
The standard name for the output path when a step fails is called error
, which happens to also be the name of the log level here, but these are independent values.
You can specify multiple types of outputs and their log levels. For example, if you also want to output success steps as debug, set it as shown: config.yaml
logged_outputs:\n error:\n level: error\n success:\n level: debug\n
Note: If you set the level lower than the general log level shown above, it will not show up in the output.
"},{"location":"arcaflow/workflows/","title":"Creating Arcaflow workflows","text":"Arcaflow workflows consist of four parts:
Version The schema version must be at the root of your workflow file. It indicates the semantic version of the workflow file structure being used.
Learn more about versioning \u00bb
Inputs The input section of a workflow is much like a plugin schema: it describes the data model of the workflow itself. This is useful because the input can be validated ahead of time. Any input data can then be referenced by the individual plugin steps.
Learn more about inputs \u00bb
Steps Steps hold the individual parts of the workflow. You can feed data from one step to the next, or feed data from the input to a step.
Learn more about steps \u00bb
Outputs Outputs hold the final result of a workflow. Outputs can reference outputs of steps.
Learn more about output \u00bb
Schema Names Learn more about our schema naming conventions \u00bb
"},{"location":"arcaflow/workflows/expressions/","title":"Arcaflow expressions","text":"Arcaflow expressions were inspired by JSONPath but have diverged from the syntax. You can use expressions in a workflow YAML like this:
some_value: !expr $.your.expression.here\n
This page explains the language elements of expressions.
Warning
Expressions in workflow definitions must be prefixed with !expr
, otherwise their literal value will be taken as a string.
"},{"location":"arcaflow/workflows/expressions/#literals","title":"Literals","text":"Literals represent constant values in an expression.
"},{"location":"arcaflow/workflows/expressions/#string-values","title":"String values","text":"Normal string literals start and end with a matched pair of either single quotes ('
) or double quotes (\"
) and have zero or more characters between the quotes.
Strings may contain special characters. In normal strings, these characters are represented by \u201cescape sequences\u201d consisting of a backslash followed by another character. Since a backslash therefore has a special meaning, in order to represent a literal backslash character, it must be preceded by another backslash. Similarly, in a string delimited by double quotes, a double quote occurring inside the string must be escaped to prevent it from marking the end of the string. The same is true for single quotes occurring inside a string delimited by single quotes. However, you do not need to escape double quotes in a single-quoted string nor single-quotes in a double-quoted string.
Here is the list of supported escape characters:
Escape Result \\\\
\\
backslash character \\t
tab character \\n
newline character \\r
carriage return character \\b
backspace character \\\"
\"
double quote character \\'
'
single quote character \\0
null character For example, to have the following text represented in a single string:
test test2/\\
You would need the expression \"test\\ntest2/\\\\\"
"},{"location":"arcaflow/workflows/expressions/#string-expressions-in-yaml","title":"String Expressions in YAML","text":"When expressing string literals in YAML, be aware that YAML has its own rules around the use of quotation marks.
For example, to include a double-quoted string in an expression, you must either add single quotes around the expression or use block flow scalars. Inside a single-quoted string, an apostrophe needs to be preceded by another apostrophe to indicate that it does not terminate the string.
Here is an example of the following value represented in a few of the various ways:
Here\u2019s an apostrophe and \u201cembedded quotes\u201d.
Inlined with single quotes:
some_value_1: !expr '\"Here''s an apostrophe and \\\"embedded quotes\\\".\"'\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The single quotes cause the YAML processor to pass the contents of the string intact except for replacing the repeated apostrophe with a single one. (They are not included in the expression value.)
- The backslash-escapes are replaced by Arca\u2019s expression processing. (The unescaped double quotes are not included in the expression value.)
Inlined with double quotes:
some_value_2: !expr \"'Here\\\\'s an apostrophe and \\\"embedded quotes\\\".'\"\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The double quotes cause the YAML processor to interpret the contents of the string:
- the
\\\\
is replaced with a single backslash; - each
\\\"
is replaced with a literal \"
; - the surrounding double quotes are not included in the expression value.
- The backslash-escapes are replaced by Arca\u2019s expression processing. (The unescaped single quotes are not included in the expression value.)
With Block Flow Scalar:
some_value_1: !expr |-\n 'Here\\'s an apostrophe and \"embedded quotes\".'\nsome_value_2: !expr |-\n \"Here's an apostrophe and \\\"embedded quotes\\\".\"\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The vertical bar (
|
) causes the YAML processor to pass the contents of the string without modification. - Newlines within the expression are included in the string; the hyphen (
-
) after the vertical bar causes the trailing newline to be omitted from the end of the string. - The backslash-escapes are replaced by Arca\u2019s expression processing. The unescaped quotes are not included in the expression value; the other quotes are escaped to prevent them from ending the string prematurely; the double quotes in
some_value_1
do not need to be escaped nor do the single quotes in some_value_2
.
See Raw string values to see how to do this without escaping.
"},{"location":"arcaflow/workflows/expressions/#raw-string-values","title":"Raw string values","text":"Raw string literals start and end with backtick characters \u201c`\u201c.
In a raw string, all characters are interpreted literally. This means that you can use '
and \"
characters without escaping them, and backslashes are treated like any other character. However, backtick characters cannot appear in a raw string.
Here is an example of the following value represented using raw strings:
Here\u2019s an apostrophe and \u201cembedded quotes\u201d.
Inlined:
some_value: !expr '`Here''s an apostrophe and \"embedded quotes\".`'\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The single quotes cause the YAML processor to pass the contents of the string intact except for replacing the repeated apostrophe with a single one. (They are not included in the expression value.)
- The backticks cause Arca\u2019s expression processing to use the string verbatim. (The backticks are not included in the expression value.)
With Block Flow Scalar:
some_value: !expr |-\n `Here's an apostrophe and \"embedded quotes\".`\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The vertical bar (
|
) causes the YAML processor to pass the contents of the string without modification. - Newlines within the expression are included in the string; the hyphen (
-
) after the vertical bar causes the trailing newline to be omitted from the end of the string. - The backticks cause Arca\u2019s expression processing to use the string verbatim, thus the embedded quotes don\u2019t require escapes. (The backticks are not included in the expression value.)
"},{"location":"arcaflow/workflows/expressions/#integer-numbers","title":"Integer numbers","text":"Integers are whole numbers expressed as sequences of base-10 digits.
Integer literals may not start with 0
, unless the value is 0
. For example, 001
is not a valid integer literal.
Examples:
0
1
503
Negative values are constructed by applying the negation operator (-
) to a literal numeric value.
"},{"location":"arcaflow/workflows/expressions/#floating-point-numbers","title":"Floating point numbers","text":"Floating point literals are non-negative double-precision floating point numbers.
Supported formats include:
- number characters followed by a period followed by zero or more number characters:
1.1
or 1.
- base-10 exponential scientific notation formats like
5.0e5
and 5.0E-5
Negative values are constructed by applying the negation operator (-
) to a literal numeric value.
"},{"location":"arcaflow/workflows/expressions/#boolean-values","title":"Boolean values","text":"Boolean literals have two valid values:
true
false
No other values are valid boolean literals. The values are case-sensitive.
"},{"location":"arcaflow/workflows/expressions/#root-reference","title":"Root reference","text":"The $
character always references the root of the data structure. Let\u2019s take this data structure:
foo:\n bar: Hello world!\n
You can reference the text like this:
$.foo.bar\n
"},{"location":"arcaflow/workflows/expressions/#dot-notation","title":"Dot notation","text":"The dot notation allows you to reference fields of an object.
For example, if you have an object on the root data structure named \u201ca\u201d with the field \u201cb\u201d in it, you can access it with:
$.a.b\n
"},{"location":"arcaflow/workflows/expressions/#bracket-accessor","title":"Bracket accessor","text":"The bracket accessor is used for referencing values in maps or lists.
"},{"location":"arcaflow/workflows/expressions/#list-access","title":"List access","text":"For list access, you specify the index of the value you want to access. The index should be an expression yielding a non-negative integer value, where zero corresponds to the first value in the list.
If you have a list named foo
:
foo:\n - Hello world!\n
You can access the first value with the expression:
$.foo[0]\n
Giving the output \"Hello world!\"
"},{"location":"arcaflow/workflows/expressions/#map-access","title":"Map access","text":"Maps, also known as dictionaries in some languages, are key-value pair data structures.
To use a map in an expression, the expression to the left of the brackets must be a reference to a map. That is then followed by a pair of brackets with a sub-expression between them. That sub-expression must evaluate to a valid key in the map.
Here is an example of a map with string keys and integer values. The map is stored in a field called foo
in the root-level object:
foo:\n a: 1\n b: 2\n
Given the map shown above, the following expression would yield a value of 2
:
$.foo[\"b\"]\n
"},{"location":"arcaflow/workflows/expressions/#functions","title":"Functions","text":"The engine provides predefined functions for use in expressions. These provide transformations beyond what is available from operators.
Functions:
function definition return type description intToFloat(integer)
float Converts an integer value into the equivalent floating point value. floatToInt(float)
integer Converts a floating point value into an integer value by discarding the fraction, rounding toward zero to the nearest integer.Special cases:\u00a0 +Inf yields the maximum 64-bit integer (9223372036854775807)\u00a0 -Inf and NaN yield the minimum 64-bit integer (-9223372036854775808)For example, 5.5
yields 5
, and -1.9
yields -1
intToString(integer)
string Returns a string containing the base-10 representation of the input.For example, an input of 55
yields \"55\"
floatToString(float)
string Returns a string containing the base-10 representation of the input.For example, an input of 5000.5
yields \"5000.5\"
floatToFormattedString(float, string, integer)
string Returns a string containing the input in the specified format with the specified precision.\u00a0 Param 1: the floating point input value\u00a0 Param 2: the format specifier: \"e\"
, \"E\"
, \"f\"
, \"g\"
, \"G\"
\u00a0 Param 3: the number of digitsSpecifying -1 for the precision will produce the minimum number of digits required to represent the value exactly. (See the Go runtime documentation for details.) boolToString(boolean)
string Returns \"true\"
for true
, and \"false\"
for false
. stringToInt(string)
integer Interprets the string as a base-10 integer. Returns an error if the input is not a valid integer. stringToFloat(string)
float Converts the input string to a double-precision floating-point number.Accepts floating-point numbers as defined by the Go syntax for floating point literals. If the input is well-formed and near a valid floating-point number, returns the nearest floating-point number rounded using IEEE754 unbiased rounding. Returns an error when an invalid input is received. stringToBool(string)
boolean Interprets the input as a boolean.Accepts \"1\"
, \"t\"
, and \"true\"
as true
and \"0\"
, \"f\"
, and \"false\"
as false
(case is not significant). Returns an error for any other input. ceil(float)
float Returns the least integer value greater than or equal to the input.Special cases are:\u00a0 ceil(\u00b10.0) = \u00b10.0\u00a0 ceil(\u00b1Inf) = \u00b1Inf\u00a0 ceil(NaN) = NaNFor example ceil(1.5)
yields 2.0
, and ceil(-1.5)
yields -1.0
floor(float)
float Returns the greatest integer value less than or equal to the input.Special cases are:\u00a0 floor(\u00b10.0) = \u00b10.0\u00a0 floor(\u00b1Inf) = \u00b1Inf\u00a0 floor(NaN) = NaNFor example floor(1.5)
yields 1.0
, and floor(-1.5)
yields -2.0
round(float)
float Returns the nearest integer to the input, rounding half away from zero.Special cases are:\u00a0 round(\u00b10.0) = \u00b10.0\u00a0 round(\u00b1Inf) = \u00b1Inf\u00a0 round(NaN) = NaNFor example round(1.5)
yields 2.0
, and round(-1.5)
yields -2.0
abs(float)
float Returns the absolute value of the input.Special cases are:\u00a0 abs(\u00b1Inf) = +Inf\u00a0 abs(NaN) = NaN toLower(string)
string Returns the input with Unicode letters mapped to their lower case. toUpper(string)
string Returns the input with Unicode letters mapped to their upper case. splitString(string, string)
list[string] Returns a list of the substrings which appear between instances of the specified separator; the separator instances are not included in the resulting list elements; adjacent occurrences of separator instances as well as instances appearing at the beginning or ending of the input will produce empty string list elements.\u00a0 Param 1: The string to split.\u00a0 Param 2: The separator. readFile(string)
string Returns the contents of a file as a UTF-8 character string, given a file path string. Relative file paths are resolved from the Arcaflow process working directory. Shell environment variables are not expanded. bindConstants(list[any], any)
list[object] Returns a list of objects each containing two properties: an item
property which contains the corresponding item from the list in the first parameter; and, a constant
property which contains the value of the second input parameter. The output list items will have a generated schema name as described in Generated Schema Names. For usage see this example. A function is used in an expression by referencing its name followed by a comma-separated list of zero or more argument expressions enclosed in parentheses.
Example:
thisIsAFunction(\"this is a string literal for the first parameter\", $.a.b)\n
"},{"location":"arcaflow/workflows/expressions/#binary-operations","title":"Binary Operations","text":"Binary Operations have an expression to the left and right, with an operator in between. The order of operations determines which operators are evaluated first. See Order of Operations
The types of the left and right operand expressions must match. To convert between types, see the list of available functions. The type of the resulting expression is the same as the type of its operands.
Operator Description +
Addition/Concatenation -
Subtraction *
Multiplication /
Division %
Modulus ^
Exponentiation ==
Equal To !=
Not Equal To >
Greater Than <
Less Than >=
Greater Than or Equal To <=
Less Than or Equal To &&
Logical And \\|\\|
Logical Or"},{"location":"arcaflow/workflows/expressions/#additionconcatenation","title":"Addition/Concatenation","text":"This operator has different behavior depending on the type.
"},{"location":"arcaflow/workflows/expressions/#string-concatenation","title":"String Concatenation","text":"When the +
operator is used with two strings, it concatenates them together. For example, the expression \"a\" + \"b\"
would output the string \"ab\"
.
"},{"location":"arcaflow/workflows/expressions/#mathematical-addition","title":"Mathematical Addition","text":"When the +
operator is used with numerical operands, it adds them together. The operator requires numerical operands with the same type. You cannot mix float and integer operands. For example, the expression 2 + 2
would output the integer 4
.
"},{"location":"arcaflow/workflows/expressions/#subtraction","title":"Subtraction","text":"When the -
operator is applied to numerical operands, the result is the value of the right operand subtracted from the value of the left. The operator requires numerical operands with the same type. You cannot mix float and integer operands.
For example, the expression 6 - 4
would output the integer 2
. The expression $.a - $.b
would evaluate the values of a
and b
within the root, and subtract the value of $.b
from $.a
.
"},{"location":"arcaflow/workflows/expressions/#multiplication","title":"Multiplication","text":"When the *
operator is used with numerical operands, it multiplies them. The operator requires numerical operands with the same type.
For example, the expression 3 * 3
would output the integer 9
.
"},{"location":"arcaflow/workflows/expressions/#division","title":"Division","text":"When the /
operator is used with numerical operands, it outputs the value of the left expression divided by the value of the right. The operator requires numerical operands with the same type.
The result of integer division is rounded towards zero. If a non-integral result is required, or if different rounding logic is required, convert the inputs into floating point numbers with the intToFloat
function. Different types of rounding can be performed on floating point numbers with the functions ceil
, floor
, and round
.
For example, the expression -3 / 2
would yield the integer value -1
.
"},{"location":"arcaflow/workflows/expressions/#modulus","title":"Modulus","text":"When the %
operator is used with numerical operands, it evaluates to the remainder when the value of the left expression is divided by the value of the right. The operator requires numerical operands with the same type.
For example, the expression 5 % 3
would output the integer 2
.
"},{"location":"arcaflow/workflows/expressions/#exponentiation","title":"Exponentiation","text":"The ^
operator outputs the result of the left side raised to the power of the right side. The operator requires numerical operands with the same type.
The mathematical expression 23 is represented in the expression language as 2^3
, which would output the integer 8
.
"},{"location":"arcaflow/workflows/expressions/#equal-to","title":"Equal To","text":"The ==
operator evaluates to true if the values of the left and right operands are the same. Both operands must have the same type. You may use functions to convert between types \u2013 see functions for more type conversions. The operator supports the types integer
, float
, string
, and boolean
.
For example, 2 == 2
results in true
, and \"a\" == \"b\"
results in false
. 1 == 1.0
would result in a type error.
"},{"location":"arcaflow/workflows/expressions/#not-equal-to","title":"Not Equal To","text":"The !=
operator is the inverse of the ==
operator. It evaluates to false if the values of the left and right operands are the same. Both operands must have the same type. You may use functions to convert between types \u2013 see functions for more type conversions. The operator supports the types integer
, float
, string
, and boolean
.
For example, 2 != 2
results in false
, and \"a\" != \"b\"
results in true
. 1 != 1.0
would result in a type error.
"},{"location":"arcaflow/workflows/expressions/#greater-than","title":"Greater Than","text":"The >
operator outputs true
if the left side is greater than the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For an integer example, the expression 3 > 3
would output the boolean false
, and 4 > 3
would output true
. For a string example, the expression \"a\" > \"b\"
would output false
.
"},{"location":"arcaflow/workflows/expressions/#less-than","title":"Less Than","text":"The <
operator outputs true
if the left side is less than the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For an integer example, the expression 3 < 3
would output the boolean false
, and 1 < 2
would output true
. For a string example, the expression \"a\" < \"b\"
would output true
.
"},{"location":"arcaflow/workflows/expressions/#greater-than-or-equal-to","title":"Greater Than or Equal To","text":"The >=
operator outputs true
if the left side is greater than or equal to (not less than) the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For an integer example, the expression 3 >= 3
would output the boolean true
, 3 >= 4
would output false
, and 4 >= 3
would output true
.
"},{"location":"arcaflow/workflows/expressions/#less-than-or-equal-to","title":"Less Than or Equal To","text":"The <=
operator outputs true
if the left side is less than or equal to (not greater than) the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For example, the expression 3 <= 3
would output the boolean true
, 3 <= 4
would output true
, and 4 <= 3
would output false
.
"},{"location":"arcaflow/workflows/expressions/#logical-and","title":"Logical AND","text":"The &&
operator returns true
if both the left and right sides are true
, and false
otherwise. This operator requires boolean operands. Note: The operation does not \u201cshort-circuit\u201d \u2013 both the left and right expressions are evaluated before the comparison takes place.
All cases:
Left Right &&
true
true
true
true
false
false
false
true
false
false
false
false
"},{"location":"arcaflow/workflows/expressions/#logical-or","title":"Logical OR","text":"The ||
operator returns true
if either or both of the left and right sides are true
, and false
otherwise. This operator requires boolean operands. Note: The operation does not \u201cshort-circuit\u201d \u2013 both the left and right expressions are evaluated before the comparison takes place.
All cases:
Left Right \\|\\|
true
true
true
true
false
true
false
true
true
false
false
false
"},{"location":"arcaflow/workflows/expressions/#unary-operations","title":"Unary Operations","text":"Unary operations are operations that have one input. The operator is applied to the expression which follows it.
Operator Description - Negation ! Logical complement"},{"location":"arcaflow/workflows/expressions/#negation","title":"Negation","text":"The -
operator negates the value of the expression which follows it.
This operation requires numeric input.
Examples with integer literals: -5
, - 5
Example with a float literal: -50.0
Example with a reference: -$.foo
Example with parentheses and a sub-expression: -(5 + 5)
"},{"location":"arcaflow/workflows/expressions/#logical-complement","title":"Logical complement","text":"The !
operator logically inverts the value of the expression which follows it.
This operation requires boolean input.
Example with a boolean literal: !true
Example with a reference: !$.foo
"},{"location":"arcaflow/workflows/expressions/#parentheses","title":"Parentheses","text":"Parentheses are used to force precedence in the expression. They do not do anything implicitly (for example, there is no implied multiplication).
For example, the expression 5 + 5 * 5
evaluates the 5 * 5
before the +
, resulting in 5 + 25
, and finally 30
. If you want the 5 + 5 to be run first, you must use parentheses. That gives you the expression (5 + 5) * 5
, resulting in 10 * 5
, and finally 50
.
"},{"location":"arcaflow/workflows/expressions/#order-of-operations","title":"Order of Operations","text":"The order of operations is designed to match mathematics and most programming languages.
Order (highest to lowest; operators listed on the same line are evaluated in the order they appear in the expression):
- negation (
-
) - parentheses (
()
) - exponent (
^
) - multiplication (
*
) and division (/
) - addition/concatenation (
+
) and subtraction (-
) -
binary equality and inequality (all equal)
- equals (
==
) - not equals (
!=
) - greater than (
>
) - less than (
<
) - greater than or equal to (
>=
) - less than or equal to (
<=
)
-
logical complement (!
)
- logical AND (
&&
) - logical OR (
||
) - dot notation (
.
) and bracket access ([]
) "},{"location":"arcaflow/workflows/expressions/#other-information","title":"Other information","text":"More information on the expression language is available in the development guide.
"},{"location":"arcaflow/workflows/expressions/#examples","title":"Examples","text":""},{"location":"arcaflow/workflows/expressions/#referencing-inputs","title":"Referencing inputs","text":"Pass a workflow input directly to a plugin input
workflow.yaml
version: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n name:\n type:\n type_id: string\n\nsteps:\n step_a:\n plugin:\n deployment_type: image\n src: quay.io/some/container/image\n input:\n some:\n key: !expr $.input.name\n
"},{"location":"arcaflow/workflows/expressions/#passing-between-steps","title":"Passing between steps","text":"Pass output from one plugin to the input of another plugin
workflow.yamlversion: v0.2.0\nsteps:\n step_a:\n plugin: \n deployment_type: image\n src: quay.io/some/container/image\n input: {}\n\n step_b:\n plugin:\n deployment_type: image \n src: quay.io/some/container/image\n input:\n some:\n key: !expr $.steps.step_a.outputs.success.some_value\n
"},{"location":"arcaflow/workflows/expressions/#binding-constants","title":"Binding Constants","text":"input.yamlrepeated_inputs: \n hostname: mogo\nvarying_inputs:\n - cpu_load: 10\n - cpu_load: 20\n - cpu_load: 40\n - cpu_load: 60\n
workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n repeated_inputs:\n type:\n type_id: ref\n id: RepeatedValues\n varying_inputs:\n type:\n type_id: list\n items:\n id: SubRootObject\n type_id: ref\n RepeatedValues:\n id: RepeatedValues\n properties:\n hostname:\n type:\n type_id: string\n SubRootObject:\n id: SubRootObject\n properties:\n cpu_load:\n type:\n type_id: integer \n\nsteps:\n example:\n plugin:\n deployment_type: image \n src: quay.io/some/container/image\n input: !expr 'bindConstants($.input.varying_inputs, $.input.repeated_inputs)'\n
In this case, we do not need to know the schema name of the type output generated by bindConstants()
. If you need to reference the schema of the list items returned by bindConstants()
, see Generated Schema Name.
"},{"location":"arcaflow/workflows/flow-control/","title":"Using Flow Control Mechanics","text":"Flow control allows the workflow author to build a workflow with a decision tree based on supported flow logic. These flow control operations are not implemented by plugins, but are part of the workflow engine itself.
"},{"location":"arcaflow/workflows/flow-control/#foreach-loops","title":"Foreach Loops","text":"Foreach loops allow for running a sub-workflow with iterative inputs from a parent workflow. A sub-workflow is a complete Arcaflow workflow file with its own input and output schemas as described in this section. The inputs for the sub-workflow are provided as a list, where each list item is an object that matches the sub-workflow input schema.
Tip
A complete functional example is available in the arcaflow-workflows repository.
In the parent workflow file, the author can define an input schema with the list that will contain the input object that will be passed to the sub-workflow. For example:
workflow.yamlinput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n loop:\n type:\n type_id: list\n items:\n type_id: object\n id: loop_id\n properties:\n loop_id:\n type:\n type_id: integer\n param_1:\n type:\n type_id: integer\n param_2:\n type:\n type_id: string\n
Then in the steps
section of the workflow, the sub-workflow can be defined as a step with the loop
list object from above passed to its input.
The parameters for the sub-workflow step are:
kind
- The type of loop (currently only foreach is supported) items
- A list of objects to pass to the sub-workflow (the expression language allows to pass this from the input schema per the above example) workflow
- The file name for the sub-workflow (this should be in the same directory as the parent workflow) parallelism
- The number of sub-workflow loop iterations that will run in parallel
workflow.yamlsteps:\n sub_workflow_loop:\n kind: foreach\n items: !expr $.input.loop\n workflow: sub-workflow.yaml\n parallelism: 1\n
The input yaml file for the above parent workflow would provide the list of objects to loop over as in this example:
input.yamlloop:\n - loop_id: 1\n param_1: 10\n param_2: \"a\"\n - loop_id: 2\n param_1: 20\n param_2: \"b\"\n - loop_id: 3\n param_1: 30\n param_2: \"c\"\n
The sub-workflow file then has its complete schema and set of steps as in this example:
sub-workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n loop_id:\n type:\n type_id: integer\n param_1:\n type:\n type_id: integer\n param_2:\n type:\n type_id: string\nsteps:\n my_plugin:\n plugin: \n deployment_type: image\n src: path/to/my_plugin:1\n input:\n param_1: !expr $.input.param_1\n my_other_plugin:\n plugin: \n deployment_type: image\n src: path/to/my_other_plugin:1\n input:\n param_2: !expr $.input.param_2\noutputs:\n success:\n loop_id: !expr $.input.loop_id\n my_plugin: !expr $.steps.my_plugin.outputs.success\n my_other_plugin: !expr $.steps.my_other_plugin.outputs.success\n
"},{"location":"arcaflow/workflows/flow-control/#reduce-repetition-with-bindconstants","title":"Reduce Repetition with bindConstants()
","text":"The builtin function bindConstants()
allows you to avoid repeating input variables for a foreach
subworkflow. In the example below, the input variable name
\u2019s value is repeated across each iteration in this input. This results in a more repetitive input and schema definition. This section will show you how to simplify it.
"},{"location":"arcaflow/workflows/flow-control/#workflow-and-input-before-bindconstants","title":"Workflow and Input Before bindConstants()
","text":"input-repeated.yamliterations:\n - loop_id: 1\n repeated_inputs:\n name: mogo\n ratio: 3.14\n - loop_id: 2\n repeated_inputs:\n name: mogo\n ratio: 3.14\n - loop_id: 3\n repeated_inputs:\n name: mogo\n ratio: 3.14\n - loop_id: 4\n repeated_inputs:\n name: mogo\n ratio: 3.14\n
workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n iterations:\n type:\n type_id: list\n items:\n id: SubRootObject\n type_id: ref\n namespace: $.steps.foreach_loop.execute.inputs.items\n\nsteps:\n foreach_loop:\n kind: foreach\n items: !expr $.input.iterations\n workflow: subworkflow.yaml\n parallelism: 1\n\noutputs:\n success:\n fab_four: !expr $.steps.foreach_loop.outputs.success.data\n
subworkflow.yamlversion: v0.2.0\ninput:\n root: SubRootObject\n objects:\n SubRootObject:\n id: SubRootObject\n properties:\n loop_id:\n type:\n type_id: integer\n repeated_inputs:\n type:\n type_id: ref\n id: RepeatedInputs\n RepeatedInputs:\n id: RepeatedInputs\n properties:\n name:\n type:\n type_id: string\n ratio:\n type:\n type_id: float \n\nsteps:\n example:\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-template-python:0.4.0\n input:\n name: !expr $.input.repeated_inputs.name\n\noutputs:\n success:\n loop_id: !expr $.input.loop_id\n ratio: !expr $.input.repeated_inputs.ratio\n beatle: !expr $.steps.example.outputs.success\n
"},{"location":"arcaflow/workflows/flow-control/#reduced-repetition-workflow","title":"Reduced Repetition Workflow","text":"Here we restructure the input, factoring out the repeated name
and ratio
entries in the list and placing them into a single field; we will use bindConstants()
to construct the foreach
list with repeated entries.
input.yamlrepeated_inputs: \n name: mogo\n ratio: 3.14\niterations:\n - loop_id: 1\n - loop_id: 2\n - loop_id: 3\n - loop_id: 4\n
To use the generated values from bindConstants()
, a new schema representing these bound values must be added to the input schema section of our subworkflow.yaml
, input
. This new schema\u2019s ID will be the ID of the schema that defines the items in your list, in this case SubRootObject
and the schema name that defines your repeated inputs, in this case RepeatedValues
, for more information see Generated Schema Names. This creates our new schema ID, SubRootObject__RepeatedValues
. You are required to use this schema ID because it is generated from the names of your other schemas.
workflow.yamlsteps:\n foreach_loop:\n kind: foreach\n items: !expr 'bindConstants($.input.iterations, $.input.repeated_inputs)'\n workflow: subworkflow.yaml\n parallelism: 1\n
To use bindConstants()
with an outputSchema
in your workflow, you need to reference the schema of the list items returned by bindConstants()
, see Generated Schema Name.
See the full workflow.
"},{"location":"arcaflow/workflows/input/","title":"Writing workflow inputs","text":"The input section of a workflow is much like a plugin schema: it describes the data model of the workflow itself. This is useful because the input can be validated ahead of time. Any input data can then be referenced by the individual steps.
Tip
The workflow input schema is analogous to the plugin input schema in that it defines the expected inputs and formats. But a workflow author has the freedom to define the schema independently of the plugin schema \u2013 This means that objects can be named and documented differently, catering to the workflow user, and input validation can happen before a plugin is loaded.
The workflow inputs start with a scope object. As an overview, a scope looks like this:
workflow.yamlinput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n name:\n type:\n type_id: string\n # Other properties of the root object\n # Other objects that can be referenced here\n
This corresponds to the following workflow input:
workflow_input.yamlname: Arca Lot\n
Admittedly, this looks complicated, but read on, it will become clear very quickly.
"},{"location":"arcaflow/workflows/input/#objects","title":"Objects","text":"Let\u2019s start with objects. Objects are like structs or classes in programming. They have two properties: an ID and a list of properties. The basic structure looks like this:
some_object:\n id: some_object\n properties:\n # Properties here\n
"},{"location":"arcaflow/workflows/input/#properties","title":"Properties","text":"Now you need to define a property. Let\u2019s say, we want to define a string with the name of the user. You can do this as follows:
type_id: object\nid: some_object\nproperties:\nname:\n type:\n type_id: string\n
Notice, that the type_id
field is indented. That\u2019s because the type
field describes a string type, which has additional parameters. For example:
type_id: object\nid: some_object\nproperties:\nname:\n type:\n type_id: string\n min: 1 # Minimum length for the string\n
There are also additional attributes of the property itself. For example:
type_id: object\nid: some_object\nproperties:\nname:\n type:\n type_id: string\n min: 1 # Minimum length for the string\n display:\n name: Name\n description: Name of the user.\n conflicts:\n - full_name\n
Properties have the following attributes:
Attribute Type Description display
Display
Display metadata of the property. See Display values. required
bool
If set to true, the field must always be filled. required_if
[]string
List of other properties that, if filled, lead to the current property being required. required_if_not
[]string
List of other properties that, if not filled, lead to the current property being required. conflicts
[]string
List of other properties that conflict the current property. default
string
Default value for this property, JSON-encoded. examples
[]string
Examples for the current property, JSON-encoded. Note
Unlike the plugin schema where an unassigned default value is set to None
, for the workflow schema you simply omit the default to leave it unassigned.
"},{"location":"arcaflow/workflows/input/#scopes-and-refs","title":"Scopes and refs","text":"Scopes behave like objects, but they serve an additional purpose. Suppose, object A
had a property of the object type B
, but now you needed to reference back to object A
. Without references, there would be no way to do this.
OpenAPI and JSON Schema have a similar concept, but in those systems all references are global. This presents a problem when merging schemas. For example, both Docker and Kubernetes have an object called Volume
. These objects would need to be renamed when both configurations are in one schema.
Arcaflow has a different solution: every plugin, every part of a workflow has its own scope. When a reference is found in a scope, it always relates to its own scope. This way, references don\u2019t get mixed.
Let\u2019s take a simple example: a scope with objects A
and B
, referencing each other.
type_id: scope\nroot: A\nobjects:\n A:\n type_id: object\n id: A\n properties:\n b:\n type:\n type_id: ref\n id: B\n required: false\n B:\n type_id: object\n id: B\n properties:\n a:\n type:\n type_id: ref\n id: A\n required: false\n
This you can create a circular dependency between these objects without needing to copy-paste their properties.
Additionally, refs have an extra display
property, which references a Display value to provide context for the reference.
"},{"location":"arcaflow/workflows/input/#strings","title":"Strings","text":"Strings are, as the name suggests, strings of human-readable characters. They have the following properties:
type_id: string\nmin: # Minimum number of characters. Optional.\nmax: # Maximum number of characters. Optional.\npattern: # Regular expression this string must match. Optional.\n
"},{"location":"arcaflow/workflows/input/#pattern","title":"Pattern","text":"Patterns are special kinds of strings that hold regular expressions.
type_id: pattern\n
"},{"location":"arcaflow/workflows/input/#integers","title":"Integers","text":"Integers are similar to strings, but they don\u2019t have a pattern
field but have a units
field. (See Units.)
type_id: integer\nmin: # Minimum value. Optional.\nmax: # Maximum value. Optional.\nunits:\n # Units definition. Optional.\n
"},{"location":"arcaflow/workflows/input/#floats","title":"Floats","text":"Floating point numbers are similar to integers.
type_id: float\nmin: # Minimum value. Optional.\nmax: # Maximum value. Optional.\nunits:\n # Units definition. Optional.\n
"},{"location":"arcaflow/workflows/input/#string-enums","title":"String enums","text":"Enums only allow a fixed set of values. String enums map string keys to a display value. (See Display values.)
type_id: enum_string\nvalues:\n red:\n name: Red\n yellow:\n name: Yellow\n
"},{"location":"arcaflow/workflows/input/#integer-enums","title":"Integer enums","text":"Enums only allow a fixed set of values. Integer enums map integer keys to a display value. (See Display values.)
type_id: enum_integer\nvalues:\n 1:\n name: Red\n 2:\n name: Yellow\n
"},{"location":"arcaflow/workflows/input/#booleans","title":"Booleans","text":"Booleans can hold a true or false value.
type_id: bool\n
"},{"location":"arcaflow/workflows/input/#lists","title":"Lists","text":"Lists hold items of a specific type. You can also define their minimum and maximum size.
type_id: list\nitems:\n type_id: type of the items\n # Other definitions for list items\nmin: 1 # Minimum number of items in the list (optional)\nmax: 2 # maximum number of items in the list (optional)\n
"},{"location":"arcaflow/workflows/input/#maps","title":"Maps","text":"Maps are key-value mappings. You must define both the key and value types, whereas keys can only be strings, integers, string enums, or integer enums.
type_id: map\nkeys:\n type_id: string\nvalues:\n type_id: string\nmin: 1 # Minimum number of items in the map (optional)\nmax: 2 # maximum number of items in the map (optional)\n
"},{"location":"arcaflow/workflows/input/#one-of-string-discriminator","title":"One-of (string discriminator)","text":"One-of types allow you to specify multiple alternative objects, scopes, or refs. However, these objects must contain a common field (discriminator) and each value for that field must correspond to exactly one object type.
Tip
If the common field is not specified in the possible objects, it is implicitly added. If it is specified, however, it must match the discriminator type.
type_id: one_of_string\ndiscriminator_field_name: object_type # Defaults to: _type\ntypes:\n a:\n type_id: object\n id: A\n properties:\n # Properties of object A.\n b:\n type_id: object\n id: B\n properties:\n # Properties of object B\n
We can now use the following value as an input:
object_type: a\n# Other values for object A\n
In contrast, you can specify object_type
as b
and that will cause the unserialization to run with the properties of object B
.
"},{"location":"arcaflow/workflows/input/#one-of-integer-discriminator","title":"One-of (integer discriminator)","text":"One-of types allow you to specify multiple alternative objects, scopes, or refs. However, these objects must contain a common field (discriminator) and each value for that field must correspond to exactly one object type.
Tip
If the common field is not specified in the possible objects, it is implicitly added. If it is specified, however, it must match the discriminator type.
type_id: one_of_int\ndiscriminator_field_name: object_type # Defaults to: _type\ntypes:\n 1:\n type_id: object\n id: A\n properties:\n # Properties of object A.\n 2:\n type_id: object\n id: B\n properties:\n # Properties of object B\n
We can now use the following value as an input:
object_type: 1\n# Other values for object A\n
In contrast, you can specify object_type
as 2
and that will cause the unserialization to run with the properties of object B
.
"},{"location":"arcaflow/workflows/input/#any-types","title":"Any types","text":"Any types allow any data to pass through without validation. We do not recommend using the \u201cany\u201d type due to its lack of validation and the risk to cause runtime errors. Only use any types if you can truly handle any data that is passed.
type_id: any\n
"},{"location":"arcaflow/workflows/input/#display-values","title":"Display values","text":"Display values are all across the Arcaflow schema. They are useful to provide human-readable descriptions of properties, refs, etc. that can be used to generate nice, human-readable documentation, user interfaces, etc. They are always optional and consist of the following 3 fields:
name: Short name\ndescription: Longer description of what the item does, possibly in multiple lines.\nicon: |\n <svg ...></svg> # SVG icon, 64x64 pixels, without doctype and external references.\n
"},{"location":"arcaflow/workflows/input/#units","title":"Units","text":"Units make it easier to parse and display numeric values. For example, if you have an integer representing nanoseconds, you may want to parse strings like 5m30s
.
Units have two parameters: a base unit description and multipliers. For example:
base_unit:\n name_short_singular: B\n name_short_plural: B\n name_long_singular: byte\n name_long_plural: bytes\nmultipliers:\n 1024:\n name_short_singular: kB\n name_short_plural: kB\n name_long_singular: kilobyte\n name_long_plural: kilobytes\n # ...\n
"},{"location":"arcaflow/workflows/output/","title":"Writing workflow outputs","text":"Outputs in Arcaflow serve a dual purpose:
- They provide the desired resulting data from steps and inputs to STDOUT.
- They allow for the conditional pass/fail state of the workflow (if any defined output is not available, the workflow reports a failure).
You can define an output simply with expressions. Outputs generally include desired output parameters from individual steps, but may also include data from inputs or even static values.
output:\n some_key:\n some_other_key: !expr $.steps.some_step.outputs.success.some_value\n foo: !expr $.inputs.bar\n arca: \"flow\"\n
"},{"location":"arcaflow/workflows/output/#writing-multiple-outputs","title":"Writing multiple outputs","text":"Arcaflow can produce multiple output groups for a workflow. These output groups are mutually exclusive to each other.
A common example of two mutually exclusive events could be the availability of your data storage service. Let\u2019s assume the service is either available, or unavailable (the unavailable state also includes any states where an error is thrown during data insertion). Multiple workflow outputs allows you to plan for these two events.
In this example taken from the Arcaflow Workflows project, the success
output collects the data from the specified steps and inserts it into data storage. The no-indexing
output collects the data, the error logs, and does not store the data.
outputs:\n success:\n pcp: !expr $.steps.pcp.outputs.success\n sysbench: !expr $.steps.sysbench.outputs.success\n metadata: !expr $.steps.metadata.outputs.success\n opensearch: !expr $.steps.opensearch.outputs.success\n no-indexing:\n pcp: !expr $.steps.pcp.outputs.success\n sysbench: !expr $.steps.sysbench.outputs.success\n metadata: !expr $.steps.metadata.outputs.success\n no-index: !expr $.steps.opensearch.outputs.error\n
"},{"location":"arcaflow/workflows/schemas/","title":"Workflow Schemas","text":""},{"location":"arcaflow/workflows/schemas/#schema-names","title":"Schema Names","text":""},{"location":"arcaflow/workflows/schemas/#scalar-names","title":"Scalar Names","text":"Schemas that are not composed within an ObjectSchema do not have an Object ID
. They use a stringified version of their TypeID
for their schema name.
schema name IntEnum enum_integer StringEnum enum_string String string Pattern pattern Int integer Float float Bool bool Map map OneOfString one_of_string OneOfInt one_of_int The name of a ListSchema
is the name of the schema of its element type prefixed with list_
. For lists of lists, the schema name is the name of the inner list schema prefixed with an additional list_
.
"},{"location":"arcaflow/workflows/schemas/#list-names","title":"List Names","text":"schema name List[String] list_string List[Pattern] list_pattern List[StringEnum] list_enum_string List[Map] list_map List[OneOfInt] list_one_of_int List[List[String]] list_list_string List[List[List[String]]] list_list_list_string"},{"location":"arcaflow/workflows/schemas/#object-names","title":"Object Names","text":"The name of an ObjectSchema
is its Object ID
. A ListSchema
that has an ObjectSchema
as its item value uses the name of that ObjectSchema
.
schema object id name Object MyFirstObject MyFirstObject List[Object] MyFirstObject list_MyFirstObject List[List[Object]] MyFirstObject list_list_MyFirstObject List[List[List[Object]]] MyFirstObject list_list_list_MyFirstObject"},{"location":"arcaflow/workflows/schemas/#other-schemas","title":"Other Schemas","text":" ScopeSchema
s do not use a schema name. RefSchema
s use the schema name of the type to which they point.
"},{"location":"arcaflow/workflows/schemas/#generated-combined-schema-names","title":"Generated Combined Schema Names","text":"The name of the schema for the value returned by a given call to bindConstants()
is generated from the names of the schemas of the parameters to the call. Because the output of this function is always a list, the list_
prefix is omitted from the schema name, and only the schema name of the list\u2019s items is used. The name is formed by concatenating the name of the schema of the first parameter\u2019s list items with the name of the schema of the second parameter, separated by a double underscore __
.
first schema second schema name List[Int] Object(ID=\u201dMyFirstObject\u201d) integer__MyFirstObject List[Object(ID=\u201dMyFirstObject\u201d) ] Object(ID=\u201dConstants\u201d) MyFirstObject__Constants List[Object(ID=\u201dMyFirstObject\u201d) ] String MyFirstObject__string List[String] Int string__integer List[String] List[Object(ID=\u201dConstants\u201d)] string__list_Constants"},{"location":"arcaflow/workflows/step/","title":"Writing workflow steps","text":"If your input is complete, you can now turn to writing your workflow steps. You can connect workflow steps by using expressions. For example, if step A
has an input that needs data from step B
, Arcaflow will automatically run step B
first.
To define a step type, you can do the following:
workflow.yamlversion: v0.2.0\nsteps:\n step_a: # Specify any ID here you want to reference the step by\n plugin: \n deployment_type: image\n src: quay.io/some/container/image # This must be an Arcaflow-compatible image\n input: # specify input values as a data structure, mixing in expressions as needed\n some:\n key: !expr $.steps.step_b.outputs.success.some_value \n step_b:\n plugin: \n deployment_type: image\n src: quay.io/some/container/image\n input:\n some:\n key: !expr $.input.some_value # Reference an input value\n
"},{"location":"arcaflow/workflows/step/#plugin-steps","title":"Plugin steps","text":"Plugin steps run Arcaflow plugins in containers. They can use Docker, Podman, or Kubernetes as deployers. If no deployer is specified in the workflow, the plugin will use the local deployer.
Plugin steps have the following properties:
Property Description plugin
Full name of the container image to run. This must be an Arcaflow-compatible container image. step
If a plugin provides more than one possible step, you can specify the step ID here. deploy
Configuration for the deployer. (See below.) This can contain expressions, so you can dynamically specify deployment parameters. input
Input data for the plugin. This can contain expressions, so you can dynamically define inputs. You can reference plugin outputs in the format of $.steps.your_step_id.outputs.your_plugin_output_id.some_variable
.
"},{"location":"arcaflow/workflows/step/#deployers","title":"Deployers","text":"The deploy
key for plugins lets you control how the plugin container is deployed. You can use expressions to use other plugins (e.g. the kubeconfig plugin) to generate the deployment configuration and feed it into other steps.
DockerPodmanKubernetes You can configure the Docker deployer like this:
version: v0.2.0\nstep:\n your_step_id:\n plugin: ...\n input: ...\n deploy: # You can use expressions here\n deployer_name: docker\n connection:\n # Change this to point to a TCP-based Docker socket\n host: host-to-docker\n # Add a certificates here. This is usually needed in TCP mode.\n cacert: |\n Add your CA cert PEM here\n cert: |\n Add your client cert PEM here.\n key: |\n Add your client key PEM here.\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n network:\n # Add your network config here\n platform:\n # Add your platform config here\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Docker deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Connection (object
) Type: object
Properties cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify the Dockerd server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name for Dockerd. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z0-9./:_-]+$
Default\"npipe:////./pipe/docker_engine\"\n
Examples 'unix:///var/run/docker.sock'\n
\u201d
'npipe:////./pipe/docker_engine'\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (map[string, string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[A-Z0-9_]+$
Value type Type: string
Maximum: 32760 Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
network (reference[NetworkConfig]
) Name: Network configuration Description: Provides information about the container networking for the plugin. Required: No Referenced object: NetworkConfig (see in the Objects section below) platform (reference[PlatformConfig]
) Name: Platform configuration Description: Provides information about the container host platform for the plugin. Required: No Referenced object: PlatformConfig (see in the Objects section below) HostConfig (object
) Type: object
Properties CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) NetworkConfig (object
) Type: object
Properties None
PlatformConfig (object
) Type: object
Properties None
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
If you want to use Podman as your local deployer, you can do so like this:
version: v0.2.0\nstep:\n your_step_id:\n plugin: ...\n input: ...\n deploy: # You can use expressions here\n deployer_name: podman\n podman:\n # Change where Podman is. (You can use this to point to a shell script\n path: /path/to/your/podman\n # Change the network mode\n networkMode: host\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Podman deployer Type: scope
Root object: Config Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) Objects Config (object
) Type: object
Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (list[string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+=.+$
Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
HostConfig (object
) Type: object
Properties Binds (list[string]
) Name: Volume Bindings Description: Volumes Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+:.+$
CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) Podman (object
) Type: object
Properties cgroupNs (string
) Name: CGroup namespace Description: Provides the Cgroup Namespace settings for the container Required: No Must match pattern: ^host|ns:/proc/\\d+/ns/cgroup|container:.+|private$
containerName (string
) Name: Container Name Description: Provides name of the container Required: No Must match pattern: ^.*$
imageArchitecture (string
) Name: Podman image Architecture Description: Provides Podman Image Architecture Required: No Must match pattern: ^.*$
Default\"amd64\"\n
imageOS (string
) Name: Podman Image OS Description: Provides Podman Image Operating System Required: No Must match pattern: ^.*$
Default\"linux\"\n
networkMode (string
) Name: Network Mode Description: Provides network settings for the container Required: No Must match pattern: ^bridge:.*|host|none$
path (string
) Name: Podman path Description: Provides the path of podman executable Required: No Must match pattern: ^.*$
Default\"podman\"\n
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
The Kubernetes deployer deploys on top of Kubernetes. You can set up the deployer like this:
version: v0.2.0\nstep:\n your_step_id:\n plugin: ...\n input: ...\n deploy: # You can use expressions here\n deployer_name: kubernetes\n connection:\n host: localhost:6443\n cert: |\n Add your client cert in PEM format here.\n key: |\n Add your client key in PEM format here.\n cacert: |\n Add the server CA cert in PEM format here.\n
All options for the Kubernetes deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects AWSElasticBlockStoreVolumeSource (object
) Type: object
Properties None
AzureDiskVolumeSource (object
) Type: object
Properties None
AzureFileVolumeSource (object
) Type: object
Properties None
CSIVolumeSource (object
) Type: object
Properties None
CephFSVolumeSource (object
) Type: object
Properties None
CinderVolumeSource (object
) Type: object
Properties None
Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) ConfigMapVolumeSource (object
) Type: object
Properties None
Connection (object
) Type: object
Properties bearerToken (string
) Name: Bearer token Description: Bearer token to authenticate against the Kubernetes API with. Required: No burst (int
) Name: Burst Description: Burst value for query throttling. Required: No Minimum: 0 Default10\n
cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify Kubernetes server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name and port of the Kubernetes server Required: No Default\"kubernetes.default.svc\"\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
password (string
) Name: Password Description: Password for basic authentication. Required: No path (string
) Name: Path Description: Path to the API server. Required: No Default\"/api\"\n
qps (float
) Name: QPS Description: Queries Per Second allowed against the API. Required: No Minimum: 0 Units: queries Default5.0\n
serverName (string
) Name: TLS server name Description: Expected TLS server name to verify in the certificate. Required: No username (string
) Name: Username Description: Username for basic authentication. Required: No Container (object
) Type: object
Properties args (list[string]
) Name: Arguments Description: Arguments to the entypoint (command). Required: No List Items Type: string
command (list[string]
) Name: Command Description: Override container entry point. Not executed with a shell. Required: No Minimum items: 1 List Items Type: string
env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) image (string
) Name: Image Description: Container image to use for this container. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9_\\-:./]+$
imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 workingDir (string
) Name: Working directory Description: Override the container working directory. Required: No DownwardAPIVolumeSource (object
) Type: object
Properties None
EmptyDirVolumeSource (object
) Type: object
Properties medium (string
) Name: Medium Description: How to store the empty directory Required: No Minimum: 1 Must match pattern: ^(|Memory|HugePages|HugePages-.*)$
EnvFromSource (object
) Type: object
Properties configMapRef (object
) Name: Config map source Description: Populates the source from a config map. Required: No Properties name (string
) Name: Name Description: Name of the referenced config map. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the config map must be defined. Required: No prefix (string
) Name: Prefix Description: An optional identifier to prepend to each key in the ConfigMap. Required: No Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
secretRef (object
) Name: Secret source Description: Populates the source from a secret. Required: No Properties name (string
) Name: Name Description: Name of the referenced secret. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the secret must be defined. Required: No EphemeralVolumeSource (object
) Type: object
Properties None
FCVolumeSource (object
) Type: object
Properties None
FlexVolumeSource (object
) Type: object
Properties None
FlockerVolumeSource (object
) Type: object
Properties None
GCEPersistentDiskVolumeSource (object
) Type: object
Properties None
GlusterfsVolumeSource (object
) Type: object
Properties None
HostPathVolumeSource (object
) Type: object
Properties path (string
) Name: Path Description: Path to the directory on the host. Required: Yes Minimum: 1 Examples \"/srv/volume1\"\n
type (enum[string]
) Name: Type Description: Type of the host path. Required: No Values - `` Unset
BlockDevice
Block device CharDevice
Character device Directory
Directory DirectoryOrCreate
Create directory if not found File
File FileOrCreate
Create file if not found Socket
Socket
ISCSIVolumeSource (object
) Type: object
Properties None
NFSVolumeSource (object
) Type: object
Properties None
ObjectMeta (object
) Type: object
Properties annotations (map[string, string]
) Name: Annotations Description: Kubernetes annotations to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
generateName (string
) Name: Name prefix Description: Name prefix to generate pod names from. Required: No labels (map[string, string]
) Name: Labels Description: Kubernetes labels to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
name (string
) Name: Name Description: Pod name. Required: No namespace (string
) Name: Namespace Description: Kubernetes namespace to deploy in. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"default\"\n
PersistentVolumeClaimVolumeSource (object
) Type: object
Properties None
PhotonPersistentDiskVolumeSource (object
) Type: object
Properties None
Pod (object
) Type: object
Properties metadata (reference[ObjectMeta]
) Name: Metadata Description: Pod metadata. Required: No Referenced object: ObjectMeta (see in the Objects section below) spec (reference[PodSpec]
) Name: Specification Description: Pod specification. Required: No Referenced object: PodSpec (see in the Objects section below) PodSpec (object
) Type: object
Properties affinity (object
) Name: Affinity rules Description: Affinity rules. Required: No Properties podAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
podAntiAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
containers (list[reference[Container]]
) Name: Containers Description: A list of containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) initContainers (list[reference[Container]]
) Name: Init containers Description: A list of initialization containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) nodeSelector (map[string, string]
) Name: Labels Description: Node labels you want the target node to have. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
pluginContainer (object
) Name: Plugin container Description: The container to run the plugin in. Required: Yes Properties env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"arcaflow-plugin-container\"\n
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 volumes (list[reference[Volume]]
) Name: Volumes Description: A list of volumes that can be mounted by containers belonging to the pod. Required: No List Items Type: reference[Volume]
Referenced object: Volume (see in the Objects section below) PortworxVolumeSource (object
) Type: object
Properties None
ProjectedVolumeSource (object
) Type: object
Properties None
QuobyteVolumeSource (object
) Type: object
Properties None
RBDVolumeSource (object
) Type: object
Properties None
ScaleIOVolumeSource (object
) Type: object
Properties None
SecretVolumeSource (object
) Type: object
Properties None
StorageOSVolumeSource (object
) Type: object
Properties None
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
Volume (object
) Type: object
Properties awsElasticBlockStore (reference[AWSElasticBlockStoreVolumeSource]
) Name: AWS EBS Description: AWS Elastic Block Storage. Required: No Referenced object: AWSElasticBlockStoreVolumeSource (see in the Objects section below) azureDisk (reference[AzureDiskVolumeSource]
) Name: Azure Data Disk Description: Mount an Azure Data Disk as a volume. Required: No Referenced object: AzureDiskVolumeSource (see in the Objects section below) azureFile (reference[AzureFileVolumeSource]
) Name: Azure File Description: Mount an Azure File Service mount. Required: No Referenced object: AzureFileVolumeSource (see in the Objects section below) cephfs (reference[CephFSVolumeSource]
) Name: CephFS Description: Mount a CephFS volume. Required: No Referenced object: CephFSVolumeSource (see in the Objects section below) cinder (reference[CinderVolumeSource]
) Name: Cinder Description: Mount a cinder volume attached and mounted on the host machine. Required: No Referenced object: CinderVolumeSource (see in the Objects section below) configMap (reference[ConfigMapVolumeSource]
) Name: ConfigMap Description: Mount a ConfigMap as a volume. Required: No Referenced object: ConfigMapVolumeSource (see in the Objects section below) csi (reference[CSIVolumeSource]
) Name: CSI Volume Description: Mount a volume using a CSI driver. Required: No Referenced object: CSIVolumeSource (see in the Objects section below) downwardAPI (reference[DownwardAPIVolumeSource]
) Name: Downward API Description: Specify a volume that the pod should mount itself. Required: No Referenced object: DownwardAPIVolumeSource (see in the Objects section below) emptyDir (reference[EmptyDirVolumeSource]
) Name: Empty directory Description: Temporary empty directory. Required: No Referenced object: EmptyDirVolumeSource (see in the Objects section below) ephemeral (reference[EphemeralVolumeSource]
) Name: Ephemeral Description: Mount a volume that is handled by a cluster storage driver. Required: No Referenced object: EphemeralVolumeSource (see in the Objects section below) fc (reference[FCVolumeSource]
) Name: Fibre Channel Description: Mount a Fibre Channel volume that's attached to the host machine. Required: No Referenced object: FCVolumeSource (see in the Objects section below) flexVolume (reference[FlexVolumeSource]
) Name: Flex Description: Mount a generic volume provisioned/attached using an exec based plugin. Required: No Referenced object: FlexVolumeSource (see in the Objects section below) flocker (reference[FlockerVolumeSource]
) Name: Flocker Description: Mount a Flocker volume. Required: No Referenced object: FlockerVolumeSource (see in the Objects section below) gcePersistentDisk (reference[GCEPersistentDiskVolumeSource]
) Name: GCE disk Description: Google Cloud disk. Required: No Referenced object: GCEPersistentDiskVolumeSource (see in the Objects section below) glusterfs (reference[GlusterfsVolumeSource]
) Name: GlusterFS Description: Mount a Gluster volume. Required: No Referenced object: GlusterfsVolumeSource (see in the Objects section below) hostPath (reference[HostPathVolumeSource]
) Name: Host path Description: Mount volume from the host. Required: No Referenced object: HostPathVolumeSource (see in the Objects section below) iscsi (reference[ISCSIVolumeSource]
) Name: iSCSI Description: Mount an iSCSI volume. Required: No Referenced object: ISCSIVolumeSource (see in the Objects section below) name (string
) Name: Name Description: The name this volume can be referenced by. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
nfs (reference[NFSVolumeSource]
) Name: NFS Description: Mount an NFS share. Required: No Referenced object: NFSVolumeSource (see in the Objects section below) persistentVolumeClaim (reference[PersistentVolumeClaimVolumeSource]
) Name: Persistent Volume Claim Description: Mount a Persistent Volume Claim. Required: No Referenced object: PersistentVolumeClaimVolumeSource (see in the Objects section below) photonPersistentDisk (reference[PhotonPersistentDiskVolumeSource]
) Name: PhotonController persistent disk Description: Mount a PhotonController persistent disk as a volume. Required: No Referenced object: PhotonPersistentDiskVolumeSource (see in the Objects section below) portworxVolume (reference[PortworxVolumeSource]
) Name: Portworx Volume Description: Mount a Portworx volume. Required: No Referenced object: PortworxVolumeSource (see in the Objects section below) projected (reference[ProjectedVolumeSource]
) Name: Projected Description: Projected items for all in one resources secrets, configmaps, and downward API. Required: No Referenced object: ProjectedVolumeSource (see in the Objects section below) quobyte (reference[QuobyteVolumeSource]
) Name: quobyte Description: Mount Quobyte volume from the host. Required: No Referenced object: QuobyteVolumeSource (see in the Objects section below) rbd (reference[RBDVolumeSource]
) Name: Rados Block Device Description: Mount a Rados Block Device. Required: No Referenced object: RBDVolumeSource (see in the Objects section below) scaleIO (reference[ScaleIOVolumeSource]
) Name: ScaleIO Persistent Volume Description: Mount a ScaleIO persistent volume. Required: No Referenced object: ScaleIOVolumeSource (see in the Objects section below) secret (reference[SecretVolumeSource]
) Name: Secret Description: Mount a Kubernetes secret. Required: No Referenced object: SecretVolumeSource (see in the Objects section below) storageos (reference[StorageOSVolumeSource]
) Name: StorageOS Volume Description: Mount a StorageOS volume. Required: No Referenced object: StorageOSVolumeSource (see in the Objects section below) vsphereVolume (reference[VsphereVirtualDiskVolumeSource]
) Name: vSphere Virtual Disk Description: Mount a vSphere Virtual Disk as a volume. Required: No Referenced object: VsphereVirtualDiskVolumeSource (see in the Objects section below) VsphereVirtualDiskVolumeSource (object
) Type: object
Properties None
"},{"location":"arcaflow/workflows/versioning/","title":"Workflow schema versions","text":""},{"location":"arcaflow/workflows/versioning/#valid-version-string","title":"Valid version string","text":"All workflow schema versions conform to semantic version 2.0.0 with a major, minor, and patch version. In this document, since the prepended v
is unnecessary it is not used. However, it is required as a value for the version key in your workflow file.
Invalid version string for workflow.yaml
.
version: 0.2.0\ninput:\nsteps:\noutputs:\n
Valid version string for workflow.yaml
.
version: v0.2.0\ninput:\nsteps:\noutputs:\n
"},{"location":"arcaflow/workflows/versioning/#supported-versions","title":"Supported versions","text":" - 0.2.0
"},{"location":"arcaflow/workflows/versioning/#compatibility-matrix","title":"Compatibility Matrix","text":"Workflow schema version Arcaflow Engine release 0.2.0 0.9.0"},{"location":"arcaflow/workflows/versioning/#upgrading","title":"Upgrading","text":""},{"location":"arcaflow/workflows/versioning/#020","title":"0.2.0","text":"For the configuration file, config.yaml
, two types of deployers are now possible, image
and python
, so deployer
has become deployers
. Effectively, the type
key has become the deployer_name
key. The deployer_name
key and value are required which means you must also have either the image
key or the python
key.
deployers:\n image:\n deployer_name: docker|podman|kubernetes\n python:\n deployer_name: python\n
For your workflow file, workflow.yaml
, the version
key and value are required, and they must be at the root of the file.
version: v0.2.0\ninputs: {}\nsteps: {}\noutputs: {}\n
"},{"location":"arcalog/","title":"Arcalog: Assisted Root Cause Analysis for Your Logs","text":"Arcalog is still in early development. A scientific paper describing the project is available as a preprint.
The README contains a guide on how to use Arcalog to gather data as well as how to use the --http
flag to run a minimal user interface for downloading individual build IDs from Prow.
Pre-release developer documentation is also available if you want to use the early pre-release version of Arcalog to embed the data gathering steps into your own application.
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Arcalot","text":"The Arcalot community is dedicated to developing modular tools, plugins, and libraries with flexible implementations to be used independently or as complete end-to-end solutions. We believe in enabling automation and portability of complex tasks and in pre-validating actions to avoid costly re-runs due to late failures and incompatible data.
"},{"location":"#arcaflow","title":"Arcaflow","text":"Arcaflow is a workflow orchestration system consisting of three main components:
- The Arcaflow engine - Written in Go and delivered as a single binary
- Plugins - Delivered as Linux containers and developed with SDKs
- Workflow definitions - Written in YAML to sequence plugins and direct data
Arcaflow is highly-flexible and portable, helping you to build pipelines of actions via plugins. Plugin steps typically perform one action well, creating or manipulating data that is returned in a machine-readable format. Data is validated according to schemas as it passes through the pipeline in order to clearly diagnose type mismatch problems early. Arcaflow runs on your laptop, a jump host, or in a CI system, requiring only the Arcaflow engine binary, a workflow definition in YAML, and a compatible container runtime.
Arcaflow allows you to encapsulate and version-control expertise, making potentially very complex workflows easily portable among environments and automation systems. With an Arcaflow workflow, you can carefully craft a pipeline of actions that serves your direct needs and share that workflow virtually unchanged for others to run in different environments and CI/CD systems.
An ever-growing catalog of official plugins are maintained within the Arcalot organization and are available as versioned containers from Quay.io. You can also build your own containerized plugins using the the Arcaflow SDK, available for Python and Golang. We encourage you to contribute your plugins to the community, and you can start by adding them to the plugins incubator repo via a pull request.
"},{"location":"#documentation","title":"Documentation","text":"We work hard to bring the documentation to the user, meaning that you should find a lot of relevant documentation in the context of what you may be working on via readme files, be it the engine, the SDK, a plugin, a workflow, or a sub-component. Comprehensive documentation, developer references, and quickstart guides will always be located in the arcalot.io pages.
"},{"location":"#community","title":"Community","text":"We invite you to contribute! Check out the Issues in the individual repositories for ideas on where to get involved, or consider contributing a new plugin by starting with our python plugin template repository. Outside contributions and pull requests are of course always welcome.
If you want to get more involved with contributions, maintenance, and governance, consider joining the Arcalot Round Table (ART), our central community body. The ART currently holds bi-weekly video conference meetings. Please reach out to one of our ART chairs for more information.
You can find our general community health files like our code of conduct and contribution guidelines in the .github repository. If you have any questions or suggestions, please use the Issues in the respective repository.
"},{"location":"arcaflow/","title":"Arcaflow: The noble workflow engine","text":"Arcaflow is a workflow engine that lets you run individual steps and pass data between them. The data is validated according to a schema along the way to make sure there is no corrupt data. Arcaflow runs on your laptop, a jump host, or in a CI system and deploys plugins as containers on target systems via Docker, Podman, or Kubernetes.
Did you know?
In Docker/Kubernetes, Arcaflow only needs network access to the API, not the plugin container itself. You can safely place a restrictive firewall on most plugins.
Use cases Arcaflow is a good fit to:
- Run ad-hoc tasks across container systems
- Pass data between them
- Make sure your data is correct
- Make workflows portable with minimal dependencies
You can use Arcaflow for many things. We use it for:
- Performance and chaos testing
- Ad-hoc workflows without previous deployment
- Vendor-independent CI workflows
Get started \u00bb Contribute \u00bb
Shipping expertise Good workflows take time and expertise to develop. Often these workflows evolve organically into bespoke scripts and/or application stacks, and knowledge transfer or the ability to run the workflows in new environments can be very difficult. Arcaflow addresses this problem by focusing on being the plumbing for the workflow, standardizing on a plugin architecture for all actions, minimizing dependencies, focusing on quality, and enforcing strong typing for data passing.
Arcaflow\u2019s design can drastically simplify much of the workflow creation process, and it allows the workflow author to ensure the workflow is locked in end-to-end. A complete workflow can be version-controlled as a simple YAML file and in most cases can be expected to run in exactly the same way in any compatible environment.
Not a CI system Arcaflow is not designed to run as a persistent service nor to record workflow histories, and in most cases it is probably not the best tool to set up or manage infrastructure. For end-to-end CI needs, you should leverage a system that provides these and other features (possibly something from the Alternatives list below).
Arcaflow is, however, an excellent companion to a CI system. In many cases, building complex workflows completely within a CI environment can effectively lock you into that system because the workflow may not be easily portable outside of it or run independently by a user. An Arcaflow workflow can be easily integrated into most CI systems, so a workflow that you define once may be moved in most cases without modification to different environments or run directly by users.
Alternatives It\u2019s important that you pick the right tool for the job. Sometimes, you need something simple. Sometimes, you want something persistent that keeps track of the workflows you run over time. We have collected some common and well-known open source workflow and workflow-like engines into this list and have provided some comparisons to help you find the right tool for your needs.
Here are some of the features that make Arcaflow a unique solution to the below alternatives:
- Designed for complex branching-action workflows and parallelization
- Prioritizes data passing and management via strong typing and schemas to ensure machine readability, workflow validation, and data integrity
- Runs actions as plugins via container orchestrator APIs
- Engine is deployed as a single Golang binary, and plugins are run as containers, minimizing dependencies and maximizing portability
- Workflows are designed to be explicitly version controlled to ensure portability to other environments without code or feature drift
- Plugins can be written in a variety of languages, and plugins from different languages can be mixed into the same workflow (SDKs provided currently for Python and Golang)
Ansible Ansible is an IT automation and configuration management system. It handles configuration management, application deployment, cloud provisioning, ad-hoc task execution, network automation, and multi-node orchestration. Ansible makes complex changes like zero-downtime rolling updates with load balancers easy.
How are Arcaflow and Ansible similar?
- They both perform actions on local and remote systems using modular architectures.
- Their core engines can both run on your laptop and don\u2019t need a large server.
- You don\u2019t need to deploy them on target hosts or run them permanently.
- They both allow for passing of data between steps.
- They both use YAML to define their workflows.
- Their plugins and modules can be written in a variety of languages.
How is Ansible different?
- Ansible is well-established with a wide range of available plugins.
- Ansible runs its tasks typically as commands over remote shells.
- Ansible\u2019s approach to parallelization is in terms of executing the same tasks against different hosts in parallel (see \u201cforks\u201d and \u201cstrategy\u201d). Defining different tasks to perform in parallel is more challenging.
- Ansible is written in Python and has many dependencies on the control host, though it can be run as a container to simplify this.
- Some modules may have system requirements for python or other dependencies on the target hosts/containers. (See Ansible documentation)
- Ansible workflows may not be consistent or portable across bare metal and Kubernetes environments.
Apache Airflow Airflow is a platform to programmatically author, schedule, and monitor workflows. It is a deployed workflow engine written in Python.
How are Arcaflow and Airflow similar?
- They both run workflows that allow you to pass data between individual steps.
- They are both good a parallelizing tasks.
- They both allow you to write workflow steps in Python.
How is Airflow different?
- Airflow must be deployed and run continuously.
- Airflow needs a persistence engine (database) to store data long-term.
- Airflow workflows and operators can only be written in Python.
- Operator code is tightly coupled to Airflow.
- Operators have to explicitly consider container engines if you want to run things in a container.
Argo Workflows Argo Workflows is a container-native workflow engine for orchestrating parallel jobs on Kubernetes. Argo Workflows is implemented as a Kubernetes CRD (Custom Resource Definition).
How are Arcaflow and Argo Workflows similar?
- They can both run several steps.
- They can both run containers as workflow steps.
- They both allow for passing of data between steps.
How is Argo Workflows different?
- Argo Workflows allows you to define workflows directly in Kubernetes custom resources.
- Argo Workflows allows you to run any container image as a step.
- Argo Workflows only runs as a Kubernetes operator and cannot run outside of it.
- Argo Workflows only supports a single Kubernetes cluster.
- In order to run things in parallel, you need to hand-write a DAG
Netflix Conductor Conductor is a platform created by Netflix to orchestrate workflows that span across microservices.
How are Arcaflow and Conductor similar?
- They both allow for passing of data between steps.
- They are both good a parallelizing tasks.
How is Conductor different?
- Conductor has a user interface.
- Conductor keeps a history of jobs that have run in the past.
- Conductor must be deployed and run continuously.
- Conductor needs a persistence engine (database) to store data long-term.
- Conductor requires at least 16 GB of RAM to run everything.
- Workers must reach Conductor over HTTP and must be explicitly deployed.
Tekton Tekton is a framework for creating CI/CD systems, allowing developers to build, test, and deploy across cloud providers and on-premise systems.
How are Arcaflow and Tekton similar?
- They both have tasks and pipelines to run steps in sequence.
- They both allow for passing of data between steps.
How is Tekton different?
- Tekton is a full CI/CD system that can be coupled to your version control system without additional tools.
- Tekton has built-in supply chain security.
- Tekton allows you to run any container image without additional integrations.
- Tekton is deployed inside a Kubernetes cluster and cannot exist without it.
- Tekton has no officially-supported integrations with other platforms and tools.
qDup qDup allows shell commands to be queued up across multiple servers to coordinate performance tests. It is designed to follow the same workflow as a user at a terminal so that commands can be performed with or without qDup. Commands are grouped into re-usable scripts that are mapped to different hosts by roles.
How are Arcaflow and qDup similar?
- They are both workflow systems.
- They both run on your laptop and don\u2019t need a large server.
- You don\u2019t need to deploy them on target hosts or run them permanently.
- They can both run things on remote systems out of the box.
How is qDup different?
- qDup has advanced controls for creating loops, signaling, waiting for events, etc.
- qDup makes it very simple to run scripts in a parallelized way.
- qDup runs commands over SSH locally, or integrates with podman. There are plans to support Docker and Kubernetes in the future.
- qDup is written in Java.
"},{"location":"arcaflow/getting-started/","title":"Arcaflow Getting Started Guide","text":""},{"location":"arcaflow/getting-started/#running-workflows","title":"Running Workflows","text":"An Arcaflow workflow is a definition of steps structured together to perform complex actions. Workflows are defined as machine-readable YAML and therefore can be version-controlled and shared easily to run in different environments. A workflow is a way of encapsulating and sharing expertise and ensuring reproducible results.
The requirements for running a workflow are simple. You just need the Arcaflow engine binary, a workflow definition file, and, typically, an input file. You can also provide a config file, which allows for setting workflow defaults, such as log levels. The contents of the input and configuration files, like the workflow file, are YAML. Finally, you need an appropriate container platform, such as Podman, Docker, or Kubernetes, as the target of the workflow execution.
Note
The default container platform for the Arcaflow engine is Podman. To use another platform, a configuration file is required.
A repository of example workflows is available for reference and practice. Let\u2019s try running the basic example.
First we will clone the example workflows repository:
git clone https://github.com/arcalot/arcaflow-workflows.git\n
Then we will run the workflow, setting the workflow directory as the context, and defining the workflow, configuration, and input files to use:
arcaflow --context arcaflow-workflows/basic-examples/basic/ \\\n--workflow workflow.yaml --config config.yaml --input input.yaml\n
Arcaflow will display logs, the detail of which determined by the configuration file, and then will return the machine-readable output of the workflow in YAML format:
basic workflow output YAMLoutput_data:\n example:\n message: Hello, Arcalot!\noutput_id: success\n
It\u2019s that simple! And the basics of running a workflow are the same, whether it\u2019s this single-step hello-world example:
flowchart LR\n%% Success path\nsteps.example.deploy-->steps.example.starting\nsteps.example.running-->steps.example.outputs\nsteps.example.starting-->steps.example.running\nsteps.example.starting-->steps.example.starting.started\nsteps.example.disabled-->steps.example.disabled.output\nsteps.example.outputs-->steps.example.outputs.success\nsteps.example.enabling-->steps.example.enabling.resolved\nsteps.example.enabling-->steps.example.starting\nsteps.example.enabling-->steps.example.disabled\nsteps.example.outputs.success-->outputs.success\ninput-->steps.example.starting\nsteps.example.cancelled-->steps.example.outputs
\u2026 or a much more complex workflow like this stress-ng plus PCP data collection example:
%% Mermaid markdown workflow\nflowchart LR\n%% Success path\nsteps.pcp.enabling-->steps.pcp.disabled\nsteps.pcp.enabling-->steps.pcp.enabling.resolved\nsteps.pcp.enabling-->steps.pcp.starting\nsteps.stressng.disabled-->steps.stressng.disabled.output\nsteps.stressng.cancelled-->steps.stressng.outputs\nsteps.pre_wait.cancelled-->steps.pre_wait.outputs\nsteps.pcp.outputs.success-->outputs.success\nsteps.pcp.disabled-->steps.pcp.disabled.output\nsteps.uuidgen.outputs.success-->outputs.success\nsteps.uuidgen.outputs-->steps.uuidgen.outputs.success\nsteps.pre_wait.running-->steps.pre_wait.outputs\nsteps.pcp.starting-->steps.pcp.starting.started\nsteps.pcp.starting-->steps.pcp.running\nsteps.pcp.starting.started-->steps.pre_wait.starting\nsteps.pcp.running-->steps.pcp.outputs\nsteps.uuidgen.starting-->steps.uuidgen.starting.started\nsteps.uuidgen.starting-->steps.uuidgen.running\nsteps.pre_wait.disabled-->steps.pre_wait.disabled.output\nsteps.stressng.deploy-->steps.stressng.starting\nsteps.pcp.outputs-->steps.pcp.outputs.success\nsteps.stressng.outputs-->steps.stressng.outputs.success\nsteps.stressng.outputs-->steps.post_wait.starting\nsteps.post_wait.cancelled-->steps.post_wait.outputs\nsteps.stressng.outputs.success-->outputs.success\nsteps.pre_wait.enabling-->steps.pre_wait.enabling.resolved\nsteps.pre_wait.enabling-->steps.pre_wait.starting\nsteps.pre_wait.enabling-->steps.pre_wait.disabled\nsteps.post_wait.outputs-->steps.post_wait.outputs.success\nsteps.post_wait.outputs-->steps.pcp.cancelled\nsteps.uuidgen.disabled-->steps.uuidgen.disabled.output\nsteps.uuidgen.cancelled-->steps.uuidgen.outputs\nsteps.pcp.cancelled-->steps.pcp.outputs\nsteps.stressng.enabling-->steps.stressng.starting\nsteps.stressng.enabling-->steps.stressng.disabled\nsteps.stressng.enabling-->steps.stressng.enabling.resolved\nsteps.stressng.running-->steps.stressng.outputs\nsteps.post_wait.starting-->steps.post_wait.starting.started\nsteps.post_wait.starting-->steps.post_wait.running\nsteps.uuidgen.deploy-->steps.uuidgen.starting\nsteps.uuidgen.running-->steps.uuidgen.outputs\nsteps.post_wait.deploy-->steps.post_wait.starting\nsteps.stressng.starting-->steps.stressng.starting.started\nsteps.stressng.starting-->steps.stressng.running\nsteps.pcp.deploy-->steps.pcp.starting\nsteps.post_wait.disabled-->steps.post_wait.disabled.output\nsteps.post_wait.running-->steps.post_wait.outputs\nsteps.pre_wait.outputs-->steps.pre_wait.outputs.success\nsteps.pre_wait.outputs-->steps.stressng.starting\nsteps.uuidgen.enabling-->steps.uuidgen.enabling.resolved\nsteps.uuidgen.enabling-->steps.uuidgen.starting\nsteps.uuidgen.enabling-->steps.uuidgen.disabled\nsteps.pre_wait.starting-->steps.pre_wait.starting.started\nsteps.pre_wait.starting-->steps.pre_wait.running\nsteps.post_wait.enabling-->steps.post_wait.enabling.resolved\nsteps.post_wait.enabling-->steps.post_wait.starting\nsteps.post_wait.enabling-->steps.post_wait.disabled\nsteps.pre_wait.deploy-->steps.pre_wait.starting\ninput-->steps.stressng.starting\ninput-->steps.pcp.starting
Learn more about running workflows \u00bb
"},{"location":"arcaflow/getting-started/#writing-workflows","title":"Writing Workflows","text":"As a workflow author, you determine the steps of the workflow, how data will pass between the steps, what input is required from the workflow user, and what output will be returned. It is possible to build very complex workflows with data translations, sub-workflows, parallelisim and serialization, and multiple output paths.
Let\u2019s start with something simple. Our workflow will collect a nickname
input from the user and will pass that input to an example \u201cHello world!\u201d step. The workflow will also run a UUID generation step in parallel to the example step, and it will return both the UUID and a greeting.
In the first part of the workflow.yaml
file we will define the workflow compatibility version and the input schema for the workflow. In the input schema, we are expecting only a single input called nickname
with a type of string
.
workflow.yaml (excerpt)version: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n nickname: #<<== Input key name\n display:\n description: Just a name\n name: Name\n required: true\n type:\n type_id: string #<<== Input value type\n...\n
Next we will define the steps of the workflow. The steps are to be deployed as container images, where the src
field defines the image and tag. The arcaflow-plugin-utilities
plugin has multiple steps available, so we indicate with the step: uuid
field which step we want to run. The arcaflow-plugin-example
plugin has only one step, so the step
field is not required. The uuidgen
step requires no input, so we pass an empty object {}
to it. The example
plugin requires an input object of name
with _type
and nick
fields. We statically set the value of the _type
field in the input object of the step, and then we use the Arcaflow expression language to reference the workflow input value for nickname
as the input to the plugin\u2019s nick
field.
workflow.yaml (excerpt)...\nsteps:\n uuidgen: #<<== Step name\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-utilities:0.6.0 #<<== Container image\n step: uuid #<<== Specific plugin step\n input: {} #<<== Step does not require input\n example: #<<== Step name\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-example:0.5.0 #<<== Container image\n input:\n name:\n _type: nickname #<<== Statically-defined input\n nick: !expr $.input.nickname #<<== Referenced workflow input\n...\n
Finally we define the outputs that we expect when the workflow succeeds. The workflow will run until all of the items referenced in the success
sub-object become available or until a step that one of the items depends on fails. Once all of the items referenced in the success
sub-object become available, the engine will terminate any steps which have not yet completed, and it will return the success
sub-object. If a required step fails, then the workflow will fail.
Tip
It is possible to define multiple sub-objects for outputs
with different dependencies. In this case, the output sub-oject that has its dependencies satisfied first will be the one that returns and ends the workflow. See the documentation for more info.
workflow.yaml (excerpt)...\noutputs:\n success:\n uuid: !expr $.steps.uuidgen.outputs.success\n example: !expr $.steps.example.outputs.success\n
Our final workflow looks like this:
workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n nickname:\n display:\n description: Just a name\n name: Name\n required: true\n type:\n type_id: string\n\nsteps:\n uuidgen:\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-utilities:0.6.0\n step: uuid\n input: {}\n example:\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-example:0.5.0\n input:\n name:\n _type: nickname\n nick: !expr $.input.nickname\n\noutputs:\n success:\n uuid: !expr $.steps.uuidgen.outputs.success.uuid\n example: !expr $.steps.example.outputs.success.message\n
We will create an input file to satisfy the input schema of the workflow:
input.yamlnickname: Arcalot\n
We will also create a configuration file, setting the container deployer to Podman and the log levels to error
:
config.yamllog:\n level: error\nlogged_outputs:\n error:\n level: error\ndeployers:\n image:\n deployer_name: podman\n
And now we can run our new workflow:
Tip
The default workflow file is workflow.yaml
so we don\u2019t need to specifiy it here explicitly.
arcaflow --config config.yaml --input input.yaml\n
example workflow output YAMLoutput_data:\n example: Hello, Arcalot!\n uuid: b98909c2-4a25-4cc1-8222-3290b0621129\noutput_id: success\n
Learn more about workflow concepts \u00bb
Learn more about writing workflows \u00bb
See more example workflows \u00bb
Did you know?
Arcaflow provides Mermaid markdown in the workflow debug output that allows you to quickly visualize the workflow in a graphic format. You can grab the Mermaid graph you see in the output and put it into a Mermaid editor.
Mermaid markdownMermaid rendered flowchart flowchart LR\nsteps.uuidgen.enabling-->steps.uuidgen.starting\nsteps.uuidgen.enabling-->steps.uuidgen.disabled\nsteps.uuidgen.enabling-->steps.uuidgen.enabling.resolved\nsteps.uuidgen.outputs-->steps.uuidgen.outputs.success\nsteps.example.cancelled-->steps.example.outputs\nsteps.example.outputs.success-->outputs.success\ninput-->steps.example.starting\nsteps.uuidgen.disabled-->steps.uuidgen.disabled.output\nsteps.uuidgen.outputs.success-->outputs.success\nsteps.example.disabled-->steps.example.disabled.output\nsteps.example.running-->steps.example.outputs\nsteps.example.enabling-->steps.example.enabling.resolved\nsteps.example.enabling-->steps.example.starting\nsteps.example.enabling-->steps.example.disabled\nsteps.example.starting-->steps.example.starting.started\nsteps.example.starting-->steps.example.running\nsteps.example.deploy-->steps.example.starting\nsteps.uuidgen.deploy-->steps.uuidgen.starting\nsteps.example.outputs-->steps.example.outputs.success\nsteps.uuidgen.cancelled-->steps.uuidgen.outputs\nsteps.uuidgen.running-->steps.uuidgen.outputs\nsteps.uuidgen.starting-->steps.uuidgen.starting.started\nsteps.uuidgen.starting-->steps.uuidgen.running\n
flowchart LR\nsteps.uuidgen.enabling-->steps.uuidgen.starting\nsteps.uuidgen.enabling-->steps.uuidgen.disabled\nsteps.uuidgen.enabling-->steps.uuidgen.enabling.resolved\nsteps.uuidgen.outputs-->steps.uuidgen.outputs.success\nsteps.example.cancelled-->steps.example.outputs\nsteps.example.outputs.success-->outputs.success\ninput-->steps.example.starting\nsteps.uuidgen.disabled-->steps.uuidgen.disabled.output\nsteps.uuidgen.outputs.success-->outputs.success\nsteps.example.disabled-->steps.example.disabled.output\nsteps.example.running-->steps.example.outputs\nsteps.example.enabling-->steps.example.enabling.resolved\nsteps.example.enabling-->steps.example.starting\nsteps.example.enabling-->steps.example.disabled\nsteps.example.starting-->steps.example.starting.started\nsteps.example.starting-->steps.example.running\nsteps.example.deploy-->steps.example.starting\nsteps.uuidgen.deploy-->steps.uuidgen.starting\nsteps.example.outputs-->steps.example.outputs.success\nsteps.uuidgen.cancelled-->steps.uuidgen.outputs\nsteps.uuidgen.running-->steps.uuidgen.outputs\nsteps.uuidgen.starting-->steps.uuidgen.starting.started\nsteps.uuidgen.starting-->steps.uuidgen.running
"},{"location":"arcaflow/getting-started/#running-plugins","title":"Running Plugins","text":"Workflow steps are run via plugins, which are delivered as containers. The Arcalot community maintains an ever-growing list of official plugins, which are version-controlled and hosted in our Quay.io repository.
Plugins are designed to run independent of an Arcaflow workflow. All plugins have schema definitions for their inputs and outputs, and they perform data validation against those schemas when run. Plugins also have one or more steps, and when there are multiple steps we always need to specify which step we want to run.
Tip
Plugin steps are the fundamental building blocks for workflows.
Let\u2019s take a look at the schema for the example plugin. Passing the --schema
parameter to the plugin will return the complete schema in YAML format.
PodmanDocker podman run --rm quay.io/arcalot/arcaflow-plugin-example --schema\n
docker run --rm quay.io/arcalot/arcaflow-plugin-example --schema\n
Here we see the example plugin has one step called hello-world
, which has schemas for both its inputs and outputs.
example plugin schema YAMLsteps:\n hello-world:\n display:\n description: Says hello :)\n name: Hello world!\n id: hello-world\n input:\n objects:\n FullName:\n id: FullName\n properties:\n first_name:\n display:\n name: First name\n examples:\n - '\"Arca\"'\n required: true\n type:\n min: 1\n pattern: ^[a-zA-Z]+$\n type_id: string\n last_name:\n display:\n name: Last name\n examples:\n - '\"Lot\"'\n required: true\n type:\n min: 1\n pattern: ^[a-zA-Z]+$\n type_id: string\n InputParams:\n id: InputParams\n properties:\n name:\n display:\n description: Who do we say hello to?\n name: Name\n examples:\n - '{\"_type\": \"fullname\", \"first_name\": \"Arca\", \"last_name\": \"Lot\"}'\n - '{\"_type\": \"nickname\", \"nick\": \"Arcalot\"}'\n required: true\n type:\n discriminator_field_name: _type\n type_id: one_of_string\n types:\n fullname:\n display:\n name: Full name\n id: FullName\n type_id: ref\n nickname:\n display:\n name: Nick\n id: Nickname\n type_id: ref\n Nickname:\n id: Nickname\n properties:\n nick:\n display:\n name: Nickname\n examples:\n - '\"Arcalot\"'\n required: true\n type:\n min: 1\n pattern: ^[a-zA-Z]+$\n type_id: string\n root: InputParams\n outputs:\n error:\n error: false\n schema:\n objects:\n ErrorOutput:\n id: ErrorOutput\n properties:\n error:\n display: {}\n required: true\n type:\n type_id: string\n root: ErrorOutput\n success:\n error: false\n schema:\n objects:\n SuccessOutput:\n id: SuccessOutput\n properties:\n message:\n display: {}\n required: true\n type:\n type_id: string\n root: SuccessOutput\n
The plugin schema can also be returned in JSON format, in which case you must specify whether to return the input
or output
schema.
PodmanDocker podman run --rm quay.io/arcalot/arcaflow-plugin-example --json-schema output\n
docker run --rm quay.io/arcalot/arcaflow-plugin-example --json-schema output\n
example plugin output schema JSON{\n \"$id\": \"hello-world\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"title\": \"Hello world! outputs\",\n \"description\": \"Says hello :)\",\n \"oneof\": [\n {\n \"output_id\": {\n \"type\": \"string\",\n \"const\": \"success\"\n },\n \"output_data\": {\n \"type\": \"object\",\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"message\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n }\n },\n {\n \"output_id\": {\n \"type\": \"string\",\n \"const\": \"error\"\n },\n \"output_data\": {\n \"type\": \"object\",\n \"properties\": {\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"error\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n }\n }\n ],\n \"$defs\": {\n \"SuccessOutput\": {\n \"type\": \"object\",\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"message\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n },\n \"ErrorOutput\": {\n \"type\": \"object\",\n \"properties\": {\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"error\"\n ],\n \"additionalProperties\": false,\n \"dependentRequired\": {}\n }\n }\n}\n
A plugin takes its input as a file, but because it runs as a container, it looks for the input file in the context of the container. This means you either need to bind-mount the input file to the container, or, as in this example, pipe the input value to the plugin\u2019s file input.
input.yamlname:\n _type: nickname\n nick: Arcalot\n
Note
In order to pass the input to the container via redirection or pipe, you must pass the -i, --interactive
parameter.
Podman with pipePodman with bind mountDocker with pipeDocker with bind mount cat input.yaml | podman run -i --rm quay.io/arcalot/arcaflow-plugin-example -f -\n
podman run --rm -v ${PWD}/input.yaml:/input.yaml:z quay.io/arcalot/arcaflow-plugin-example -f /input.yaml\n
cat input.yaml | docker run -i --rm quay.io/arcalot/arcaflow-plugin-example -f -\n
docker run --rm -v ${PWD}/input.yaml:/input.yaml:z quay.io/arcalot/arcaflow-plugin-example -f /input.yaml\n
example plugin return YAMLoutput_id: success\noutput_data:\n message: Hello, Arcalot!\ndebug_logs: ''\n
Now let\u2019s generate a UUID with the utilities plugin. This plugin has multiple steps, so we need to specify which step to run. The step also requires no input, so we pass it an empty object.
Note
An input object is always required, even if a plugin step does not require input parameters.
PodmanDocker echo '{}' | podman run -i --rm quay.io/arcalot/arcaflow-plugin-utilities -s uuid -f -\n
echo '{}' | docker run -i --rm quay.io/arcalot/arcaflow-plugin-utilities -s uuid -f -\n
utilities plugin UUID step return YAMLoutput_id: success\noutput_data:\n uuid: bb08484f-6263-4317-9162-be2ae846b438\ndebug_logs: ''\n
Learn more about plugin schemas \u00bb
"},{"location":"arcaflow/getting-started/#writing-plugins","title":"Writing Plugins","text":"Of course you may have specific needs and want to author your own plugins. To aid with this, we provide SDKs in popular languages. Let\u2019s create a simple hello-world plugin using the Python SDK. We\u2019ll publish the code here, and you can find the details in the Python plugin guide.
plugin.py#!/usr/local/bin/python3\nfrom dataclasses import dataclass\nimport sys\nfrom arcaflow_plugin_sdk import plugin\n\n\n@dataclass\nclass InputParams:\n name: str\n\n\n@dataclass\nclass SuccessOutput:\n message: str\n\n\n@plugin.step(\n id=\"hello-world\",\n name=\"Hello world!\",\n description=\"Says hello :)\",\n outputs={\"success\": SuccessOutput},\n)\ndef hello_world(params: InputParams):\n return \"success\", SuccessOutput(f\"Hello, {params.name}\")\n\n\nif __name__ == \"__main__\":\n sys.exit(\n plugin.run(\n plugin.build_schema(\n hello_world,\n )\n )\n )\n
Learn more about writing Python plugins \u00bb
Next, let\u2019s create a Dockerfile
and build a container image:
Dockerfile
FROM quay.io/arcalot/arcaflow-plugin-baseimage-python-osbase\n\nADD plugin.py /\nRUN python -m pip install arcaflow_plugin_sdk\n\nENTRYPOINT [\"python\", \"/plugin.py\"]\nCMD []\n
We can now build the plugin container. PodmanDocker podman build -t example-plugin .\n
docker build -t example-plugin .\n
And finally we can run our new plugin.
PodmanDocker echo \"name: Arca Lot\" | podman run -i --rm example-plugin -f -\n
echo \"name: Arca Lot\" | docker run -i --rm example-plugin -f -\n
example plugin output YAMLoutput_id: success\noutput_data:\n message: Hello, Arca Lot\ndebug_logs: ''\n
Learn more about Packaging plugins
"},{"location":"arcaflow/getting-started/#next-steps","title":"Next steps","text":"Congratulations, you are now an Arcaflow user! Here are some things you can do next to start working with plugins and workflows:
- See our repositories of community-supported plugins \u00bb
- Get our latest plugin container builds from quay.io \u00bb
- Experiment with more advanced example workflows \u00bb
"},{"location":"arcaflow/getting-started/#keep-learning","title":"Keep learning","text":"Hungry for more? Keep digging into our docs::
- Learn more about the concepts behind Arcaflow \u00bb
- Learn how to set up Arcaflow \u00bb
- Learn how to create plugins \u00bb
- Learn how to create workflows \u00bb
Contribute to Arcaflow \u00bb
"},{"location":"arcaflow/concepts/","title":"Concepts","text":"This section of the documentation deals with theoretical concepts around Arcaflow. Fear not, it\u2019s not a university exam, but simply a description on how we designed Arcaflow the way we did and why we did so.
Architecture Get started with a primer on the core architecture of Arcaflow.
Read more about architecture \u00bb
Typing We believe in strong and static typing to avoid bugs, so Arcaflow has its own typing system.
Read more about typing \u00bb
Plugins Arcaflow is interoperable between several programming languages. Currently, we provide SDKs for Python and Go.
Read more about plugins \u00bb
Workflows Arcaflow runs several steps and connects them together into a workflow.
Read more about workflows \u00bb
Flow control (WIP) In the future, we want to add the ability to perform loops, dynamic parallelization, etc.
Read more about flow control \u00bb
"},{"location":"arcaflow/concepts/architecture/","title":"Arcaflow architecture","text":"The Arcaflow architecture consists of the following 2 keys elements:
- Plugins
- The Engine
"},{"location":"arcaflow/concepts/architecture/#engine","title":"Engine","text":"The engine is responsible for the orchestration of the workflow steps. It has several duties:
- Provide schemas for workflow files, read workflows and construct execution graphs.
- Type-check the execution graphs to make sure that the data transfers between steps are type-safe.
- Orchestrate plugin execution with Docker, Podman and Kubernetes.
- Execute the workflow, following the workflow rules.
The engine itself is designed to be run from a command line interface, possibly as a webserver, but is not designed to run in a redundant fashion. Instead of implementing redundancy itself, the engine will receive support to execute workflows in third party systems, such as Kafka.
A stretch goal for the engine is to make it fully embeddable, possibly with in-binary workflows and execution images to make them easily to ship in network-restricted environments.
"},{"location":"arcaflow/concepts/architecture/#plugins","title":"Plugins","text":"Plugins provide execution for one or more steps for a workflow. The job of a plugin is to do one thing and do it well. A plugin provides a thin layer over third party tools, or its own implementation of features. Its main job is to provide accurate input and output schema information to the engine and transform the data as needed.
For example, a plugin may output unformatted text, which a plugin has to parse and build a machine-readable data structure for that information. This reformatting of data allows the engine to pipe data between steps and reliably check the data for faults.
The current plan is to provide plugin SDKs for Python, GO, and Rust (in that order).
"},{"location":"arcaflow/concepts/architecture/#typing","title":"Typing","text":"A core element of the Arcaflow system is the typing system. Each plugin and the engine itself will provide a machine-readable data structure that describes what inputs are expected and what outputs may be produced. If you are familiar with JSON schema or OpenAPI, this is similar, and Arcaflow can produce those schema documents. However, the Arcaflow system is stricter than those industry standards to optimize for performance and simpler implementation in all supported programming languages.
"},{"location":"arcaflow/concepts/architecture/#executing-workflows","title":"Executing workflows","text":"Arcaflow workflows are orchestrated in the Engine, while plugins can be run locally or remotely on container engines. This lends itself to a Git-based workflow:
flowchart LR\n subgraph laptop[Your laptop]\n direction LR\n\n ui(UI)\n engine(Engine)\n git(Git)\n\n ui -- Workflow --> engine\n ui -- Workflow --> git -- Workflow --> engine\n engine -- Execution results --> ui\n end\n\n subgraph docker[Docker/Podman<br>on your laptop]\n direction LR\n\n plugins1(Plugin)\n\n engine -- Step execution --> plugins1\n end\n engine -- Launch plugin --> docker\n\n subgraph k8s[Kubernetes]\n direction LR\n\n plugins2(Plugin)\n\n engine -- Step execution --> plugins2\n end\n engine -- Launch plugin --> k8s\n\n apis(Other APIs)\n plugins1 --> apis\n plugins2 --> apis
"},{"location":"arcaflow/concepts/plugins/","title":"Arcaflow plugins","text":"Arcaflow is designed as an interoperable system between programming languages. Therefore, plugins are started as external processes and the communication with the plugin takes place over its standard input and output. The Arcaflow Engine passes data between the plugins as required by the workflow file.
In the vast majority of cases, plugins run inside a container, while the Arcaflow Engine itself does not. This allows Arcaflow to pass data between several Kubernetes clusters, local plugins, or even run plugins via Podman over SSH. These capabilities are built into the Arcaflow Engine with the help of deployers.
Since Arcaflow has an internal typing system, each plugin must declare at the start what input data it requires and what outputs it produces. This allows the Engine to verify that the workflow can be run, and that no invalid data is being used. If invalid data is detected, the workflow is aborted to prevent latent defects in the data.
In summary, you can think of Arcaflow as a strongly (and at some time in the future possibly statically) typed system for executing workflows, where individual plugins run in containers across several systems.
"},{"location":"arcaflow/concepts/typing/","title":"Typing system","text":"Let\u2019s say you are creating a system that measures performance. But, uh-oh! A bug has struck! Instead of returning a number, a plugin returns an empty string. Would you want that converted to a numeric 0
for a metric? Or worse yet, would you want a negative number resulting from a bug to make it into your metrics? Would you want to collect metrics for years just to find out they are all wrong?
If the answer is no, then the typing system is here to help. Each plugin or workflow in Arcaflow is required to explicitly state what data types it accepts for its fields, and what their boundaries are. When a plugin then violates its own rules, the engine makes sure that corrupt data isn\u2019t used any further.
For example, let\u2019s look at the definition of an integer:
type_id: integer\nmin: 10\nmax: 128\n
It\u2019s so simple, but it already prevents a lot of bugs: non-integers, numbers out of range.
But wait! A typing system can do more for you. For example, we can automatically generate a nice documentation from it. Let\u2019s take this object as an example:
type_id: object\nid: name\nproperties:\n name:\n type:\n type_id: string\n min: 1\n max: 256\n display:\n name: \"Name\"\n description: \"The name of the user.\"\n icon: |\n <svg ...></svg>\n
That\u2019s all it takes to render a nice form field or automatic documentation. You can read more about creating types in the plugins section or the workflows section, or see the complete typing reference in the Contributing guide.
"},{"location":"arcaflow/concepts/workflows/","title":"Arcaflow Workflows (concept)","text":"Tip
This document describes the concept of Arcaflow Workflows. We describe the process of writing a workflow in this section
"},{"location":"arcaflow/concepts/workflows/#steps","title":"Steps","text":"Workflows are a way to describe a sequence or parallel execution of individual steps. The steps are provided exclusively by plugins. The simplest workflow looks like this:
stateDiagram-v2\n [*] --> Step\n Step --> [*]
However, this is only true if the step only has one output. Most steps will at least have two possible outputs, for success and error states:
stateDiagram-v2\n [*] --> Step\n Step --> [*]: yes\n Step --> [*]: no
Plugins can declare as many outputs as needed, with custom names. The workflow engine doesn\u2019t make a distinction based on the names, all outputs are treated equal for execution.
An important rule is that one step must always end in exactly one output. No step must end without an output, and no step can end in more than one output. This provides a mechanism to direct the flow of the workflow execution.
Plugins must also explicitly declare what parameters they expect as input for the step, and the data types of these and what parameters they will produce as output.
"},{"location":"arcaflow/concepts/workflows/#interconnecting-steps","title":"Interconnecting steps","text":"When two steps are connected, they will be executed after each other:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n Step1 --> Step2\n Step2 --> [*]
Similarly, when two steps are not directly connected, they may be executed in parallel:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n [*] --> Step2\n Step1 --> [*]\n Step2 --> [*]
You can use the interconnection to direct the flow of step outputs:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n Step3: Step 3\n [*] --> Step1\n Step1 --> Step2: success\n Step1 --> Step3: error\n Step2 --> [*]\n Step3 --> [*]
"},{"location":"arcaflow/concepts/workflows/#passing-data-between-steps","title":"Passing data between steps","text":"When two steps are connected, you have the ability to pass data between them. Emblematically described:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n Step1 --> Step2: input_1 = !expr $.steps.step1.outputs.success\n Step2 --> [*]
The data type of the input on Step 2 in this case must match the result of the expression. If the data type does not match, the workflow will not be executed.
"},{"location":"arcaflow/concepts/workflows/#undefined-inputs","title":"Undefined inputs","text":"Step inputs can either be required or optional. When a step input is required, it must be configured or the workflow will fail to execute. However, there are cases when the inputs cannot be determined from previous steps. In this case, the workflow start can be connected and the required inputs can be obtained from the user when running the workflow:
stateDiagram-v2\n Step1: Step 1\n Step2: Step 2\n [*] --> Step1\n [*] --> Step2: input_1 = !expr $.input.option_1\n Step1 --> Step2: input_2 = !expr $.steps.step1.outputs.success\n Step2 --> [*]
This is typically the case when credentials, such as database access, etc. are required.
"},{"location":"arcaflow/concepts/workflows/#outputs","title":"Outputs","text":"The output for each step is preserved for later inspection. However, the workflow can explicitly declare outputs. These outputs are usable in scripted environments as a direct output of the workflow:
stateDiagram-v2\n [*] --> Step\n Step --> [*]: output_1 = !expr $.steps.step1.outputs.success
Background processes
To allow background processes idiomatically, plugins can implement the cancellation signal to be stopped by the workflow.
"},{"location":"arcaflow/concepts/workflows/#flow-control-wip","title":"Flow control (WIP)","text":"The workflow contains several flow control operations. These flow control operations are not implemented by plugins, but are part of the workflow engine itself.
"},{"location":"arcaflow/concepts/workflows/#foreach","title":"Foreach","text":"The foreach flow control allows you to loop over a sub-workflow with a list of input objects.
stateDiagram-v2\n [*] --> ForEach\n state ForEach {\n [*] --> loop_list_input\n loop_list_input --> sub_workflow\n sub_workflow --> loop_list_input\n state sub_workflow {\n [*] --> Step1\n Step1 --> [*]\n }\n sub_workflow --> [*]: Sub Output\n }\n ForEach --> [*]: Output
This feature can be configured for parallel or sequential execution.
"},{"location":"arcaflow/concepts/workflows/#condition","title":"Condition","text":"A condition is a flow control operation that controls whether or not a step can run. You can also create multiple branches with opposing logic to create a switch-case effect.
stateDiagram-v2\n classDef tag font-style:italic;\n classDef step font-weight:bold;\n\n input\n [*] --> Step1:::step\n input --> Step1: !expr $.input.enabled\n or_disabled:::tag: !ordisabled\n Step1 --> or_disabled: success\n Step1 --> or_disabled: Disabled\n or_disabled --> [*]
stateDiagram-v2\n classDef tag font-style:italic;\n classDef step font-weight:bold;\n\n state if_state <<choice>>\n Step1:::step: Step 1\n [*] --> Step1\n Step1 --> if_state\n Step2:::step: Step 2\n Step3:::step: Step 3\n if_state --> Step2: !expr $.step1.output_1 == true\n if_state --> Step3: !expr $.step1.output_1 == false\n oneof:::tag: !oneof\n Step2 --> oneof\n Step3 --> oneof\n oneof --> [*]
"},{"location":"arcaflow/concepts/workflows/#abort","title":"Abort","text":"Warning
This feature is not yet implemented.
The abort flow control is a quick way to exit out of a workflow. This is useful when entering a terminal error state and the workflow output data would be useless anyway.
stateDiagram-v2\n [*] --> Step1\n Step1 --> Abort: Output 1\n Step1 --> Step2: Output 2\n Step2 --> [*]
However, this is only required if you want to abort the workflow immediately. If you want an error case to result in the workflow failing, but whatever steps can be finished being finished, you can leave error outputs unconnected.
"},{"location":"arcaflow/concepts/workflows/#do-while","title":"Do-while","text":"Warning
This feature is not yet implemented.
A do-while block will execute the steps in it as long as a certain condition is met. The condition is derived from the output of the step or steps executed inside the loop:
stateDiagram-v2\n [*] --> DoWhile\n state DoWhile {\n [*] --> Step1\n Step1 --> [*]: output_1_condition= !expr $.step1.output_1.finished == false\n }\n DoWhile --> [*]
If the step declares multiple outputs, multiple conditions are possible. The do-while block will also have multiple outputs:
stateDiagram-v2\n [*] --> DoWhile\n state DoWhile {\n [*] --> Step1\n Step1 --> [*]: Output 1 condition\n Step1 --> [*]: Output 2 condition\n }\n DoWhile --> [*]: Output 1\n DoWhile --> [*]: Output 2
You may decide to only allow exit from a loop if one of the two outputs is satisfied:
stateDiagram-v2\n [*] --> DoWhile\n state DoWhile {\n [*] --> Step1\n Step1 --> Step1: Output 1\n Step1 --> [*]: Output 2\n }\n DoWhile --> [*]: Output 1
"},{"location":"arcaflow/concepts/workflows/#multiply","title":"Multiply","text":"Warning
This feature is not yet implemented.
The multiply flow control operation is useful when you need to dynamically execute sub-workflows in parallel based on an input condition. You can, for example, use this to run a workflow step on multiple or all Kubernetes nodes.
stateDiagram-v2\n Lookup: Lookup Kubernetes hosts\n [*] --> Lookup\n Lookup --> Multiply\n state Multiply {\n [*] --> Stresstest\n Stresstest --> [*]\n }\n Multiply --> [*]
The output of a Multiply operation will be a map, keyed with a string that is configured from the input.
Tip
You can think of a Multiply step like a variation of a for-each loop geared towards specific parallelization cases.
"},{"location":"arcaflow/concepts/workflows/#synchronize","title":"Synchronize","text":"Warning
This feature is not yet implemented.
The synchronize step attempts to synchronize the execution of subsequent steps for a specified key. The key must be a constant and cannot be obtained from an input expression.
stateDiagram-v2\n [*] --> Step1\n [*] --> Step2\n Synchronize1: Synchronize (key=a)\n Synchronize2: Synchronize (key=a)\n Step1 --> Synchronize1\n Step2 --> Synchronize2\n Synchronize1 --> Step3\n Synchronize2 --> Step4\n Step3 --> [*]\n Step4 --> [*]
"},{"location":"arcaflow/contributing/","title":"Contributing to Arcaflow","text":"First of all, welcome to the Arca Lot! Whether you are a beginner or a seasoned veteran, your contributions are most appreciated. Thank you!
Now, let\u2019s get you started. There are a number of ways you can contribute on GitHub, please check the Arcaflow project board for open issues. Additionally, here are a few repos you can contribute to:
Repository What you can do here arcalot.github.io Improve the documentation arcaflow-plugin-sdk-go Improve the Go SDK arcaflow-plugin-sdk-python Improve the Python SDK arcaflow-engine Improve the Arcaflow Engine arcaflow-engine-deployer-kubernetes Improve the Kubernetes deployment of plugins arcaflow-engine-deployer-docker Improve the Docker deployment of plugins arcaflow-engine-deployer-podman Improve the Podman deployment of plugins arcaflow-expressions Improve the Arcaflow expression language arcaflow-plugin-image-builder Improve the Arcaflow plugin packaging arcaflow-plugin-* Improve the officially supported plugins If you want to contribute regularly, why not join the Arcalot Round Table by reading our charter and signing up as a member? That way you get a voice in the decisions we make!
"},{"location":"arcaflow/contributing/#license","title":"License","text":"All code in Arcaflow is licensed under the Apache 2.0 license. The documentation is licensed under CC-BY-4.0. Please make sure you read and understand these licenses before contributing. If you are contributing on behalf of your employer, please make sure you have permission to do so.
"},{"location":"arcaflow/contributing/#principles","title":"Principles","text":"While we don\u2019t deal in absolutes (only a Sith would do that) we hold ourselves to a few key principles. There are plenty of things where we could do better in these areas, so if you find something, please open an issue. It\u2019s important!
"},{"location":"arcaflow/contributing/#the-principle-of-the-least-surprise","title":"The principle of the least surprise","text":"Sometimes, things are just hard to make user-friendly. If presented with two choices, we will always pick the one that doesn\u2019t break expectations. What would an average user expect to happen without reading the documentation? If something surprised you, please open a bug.
"},{"location":"arcaflow/contributing/#the-principle-of-nice-error-messages","title":"The principle of nice error messages","text":"When using Arcaflow, you should never be confronted with a stack trace. Error messages should always explain what went wrong and how to fix it. We know, this is a tall order, but if you see an error message that is not helpful, please open a bug.
"},{"location":"arcaflow/contributing/#the-principle-of-intern-friendliness","title":"The principle of intern-friendliness","text":"There is enough software out in the wild that requires months of training and is really hard to get into. Arcaflow isn\u2019t the easiest to learn either, see the whole typing system thing, but nevertheless, the software should be written in such a way that an intern with minimal training can sit down and do something useful with it. If something is unnecessarily hard or undocumented, you guessed it, please open a bug.
"},{"location":"arcaflow/contributing/#the-principle-of-typing","title":"The principle of typing","text":"We believe that strong and static typing can save us from bugs. This applies to programming languages just as much as it applies to workflows. We aim to make a system tell us that something is wrong before we spent several hours running it.
"},{"location":"arcaflow/contributing/#the-principle-of-testing","title":"The principle of testing","text":"Bugs? Yeah, we have those, and we want fewer of them. Since we are a community effort, we can\u2019t afford a large QA team to test through everything manually before a release. Therefore, it\u2019s doubly important that we have automated tests that run on every change. Furthermore, we want our tests to run quickly and without additional setup time. You should be able to run go test
or python -m unittest discover
and get a result within a few seconds at most. This makes it more likely that a contributor will run the tests and contribute new tests instead of waiting for CI to sort it out.
"},{"location":"arcaflow/contributing/#the-principle-of-small-piles","title":"The principle of small piles","text":"Every software is\u2026 pardon our French: crap. Ours is no exception. The difference is how big and how stinky the piles are. We aim to make the piles small, well-defined and as stink-less as possible. If we need to replace a pile with another pile, it should be easy to do so.
Translated to software engineering, we create APIs between our piles components. These APIs can be in the form of code, or in the form of a GitHub Actions workflow. A non-leaky API helps us replace one side of the API without touching the other.
"},{"location":"arcaflow/contributing/#the-principle-of-kindness-to-our-future-self","title":"The principle of kindness to our future self","text":"Writing code should be fun, most of us got into this industry because we enjoyed creating something. We want to keep this joy of creation. What kills the enthusiasm fastest is having to slog through endless pieces of obtuse code, spending hours and hours trying to accomplish a one-line change. When we write code, we want to be kind to our future selves. That\u2019s why we not only write documentation and tests for our users, we also create these for ourselves and our peers.
"},{"location":"arcaflow/contributing/deployers/","title":"Arcaflow Deployers Development Guide","text":"The Arcaflow Engine relies on deployers to execute containers. Deployers provide a binary-safe transparent tunnel of communication between a plugin and the engine. (Typically, this will be done via standard input/output, but other deployers are possible.)
The Engine and the plugin communicate via the Arcaflow Transport Protocol over this tunnel, but the deployer is unaware of the method of this communication.
Deployers are written in Go and must implement the deployer interface. Deployers are not dynamically pluggable, they must also be added to the engine code to be usable.
"},{"location":"arcaflow/contributing/engine/","title":"Arcaflow Engine Development Guide","text":"Warning
The engine is currently undergoing a major refactor. This page describes the engine post-refactor.
The Arcaflow engine is responsible for parsing a YAML workflow and executing it. It goes through several phases during execution.
"},{"location":"arcaflow/contributing/engine/#yaml-loading-phase","title":"YAML loading phase","text":"During the YAML loading phase, the engine loads the workflow YAML as raw data containing YAML nodes. We need the raw YAML nodes to access the YAML tags, which we use to turn the structure into expressions. The resulting data structure of this phase is a structure consisting of maps, lists, strings, and expression objects.
YAML
YAML, at its core, only knows three data types: maps, lists, and strings. Additionally, each entry can have a tag in the form of !foo
or !!foo
.
"},{"location":"arcaflow/contributing/engine/#basic-workflow-parsing","title":"Basic workflow parsing","text":"Once the YAML is loaded, we can take the data created and parse the workflow. This will validate the input definition and the basic step definitions and provide a more structured data. However, at this point the plugin schemas are not known yet, so any data structure related to steps is accepted as-is.
"},{"location":"arcaflow/contributing/engine/#schema-loading","title":"Schema loading","text":"The engine has an API to provide step types. These step types have the ability to provide a lifecycle and load their schema. In case of plugins, this means that the plugin is fired up briefly and its schema is queried. (See Deployers.)
"},{"location":"arcaflow/contributing/engine/#dag-construction","title":"DAG construction","text":"Once the schema is loaded, the Directed Acyclic Graph can be constructed from the expressions. Each lifecycle stage input is combed for expressions and a DAG is built.
"},{"location":"arcaflow/contributing/engine/#static-code-analysis-future","title":"Static code analysis (future)","text":"The expression library already has the facilities to inspect types, which will, in the future, provide us the ability to perform a static code analysis on the workflow. This will guarantee users that a workflow can be executed without typing problems.
"},{"location":"arcaflow/contributing/engine/#workflow-execution","title":"Workflow execution","text":"When the DAG is complete and contains no cycles, the workflow execution can proceed. The execution cycle queries lifecycle nodes that have no more inbound dependencies and runs the lifecycle. When a lifecycle stage finishes, the corresponding nodes are removed from the DAG, freeing up other nodes for execution.
"},{"location":"arcaflow/contributing/expressions/","title":"Arcaflow Expressions Development Guide","text":"The expressions library provides the engine and other potential users with a simple way to compile expressions and provide typing information about an expression result.
The library consists of two parts: the internal parser/AST and the API layer.
"},{"location":"arcaflow/contributing/expressions/#the-parser-ast","title":"The Parser / AST","text":"The expressions parser constructs an Abstract Syntax Tree from the expression which can then be walked by the API layer. The AST consists of the following node types:
"},{"location":"arcaflow/contributing/expressions/#dot-notation","title":"Dot Notation","text":"Let\u2019s say you have an expression foo.bar
. The dot notation node is the dot in the middle. The left subtree of the dot will be the entire expression left of the dot, while the right subtree will be everything to the right.
"},{"location":"arcaflow/contributing/expressions/#bracket-expression","title":"Bracket Expression","text":"Bracket expressions are expressions in the form of foo[bar]
. The left subtree will represent the expression to the left of the brackets (foo
in the example), while the right subtree will represent the subexpression within the brackets (bar
in the example).
"},{"location":"arcaflow/contributing/expressions/#binary-operations","title":"Binary Operations","text":"Binary operations include all of the operations that have a left and right subtree that do not have a special node representing them (dot notation and bracket expression are examples of special cases). Binary operations are represented by a node containing an operation and the subtrees to which the operation is applied.
"},{"location":"arcaflow/contributing/expressions/#unary-operations","title":"Unary Operations","text":"Unary operations include boolean complement !
and numeric negation -
. Unary operations are represented by a node containing an operation and the subtree to which the operation is applied. Unlike binary operations, unary operations have only one subtree.
"},{"location":"arcaflow/contributing/expressions/#identifiers","title":"Identifiers","text":"Identifiers come in two forms:
$
references the root of the data structure. - A plain string identifier from a token matching the regular expression
^\\w+$
. This may be used for accessing object fields or as function identifiers.
"},{"location":"arcaflow/contributing/expressions/#the-api-layer","title":"The API Layer","text":"The API layer provides three functions:
- Evaluate an expression against a data structure without type checking
- Provide dependency information of an expression
- Return the resulting type info of an expression when given a schema
All three functions walk the AST above and construct the required data.
"},{"location":"arcaflow/contributing/plugin-protocol/","title":"Arcaflow Plugin protocol specification (ATP)","text":"Arcaflow runs plugins locally in a container using Docker or Podman, or remotely in Kubernetes. Each plugin must be containerized and communicates with the engine over standard input/output. This document outlines the protocol the engine and the plugins use to communicate.
Hint
You do not need this page if you only intend to implement a plugin with the SDK!
"},{"location":"arcaflow/contributing/plugin-protocol/#execution-model","title":"Execution model","text":"A single plugin execution is intended to run a single task and not more. This simplifies the code since there is no need to try and clean up after each task. Each plugin is executed in a container and must communicate with the engine over standard input/output. Furthermore, the plugin must add a handler for SIGTERM
and properly clean up if there are services running in the background.
Each plugin is executed at the start of the workflow, or workflow block, and is terminated only at the end of the current workflow or workflow block. The plugin can safely rely on being able to start a service in the background and then keeping it running until the SIGTERM
comes to shut down the container.
However, the plugin must, under no circumstances, start doing work until the engine sends the command to do so. This includes starting any services inside the container or outside. This restriction is necessary to be able to launch the plugin with minimal resource consumption locally on the engine host to fetch the schema.
The plugin execution is divided into three major steps.
- When the plugin is started, it must output the current plugin protocol version and its schema to the standard output. The engine will read this output from the container logs.
- When it is time to start the work, the engine will send the desired step ID with its input parameters over the standard input. The plugin acknowledges this and starts to work. When the work is complete, the plugin must automatically output the results to the standard output.
- When a shutdown is desired, the engine will send a
SIGTERM
to the plugin. The plugin has up to 30 seconds to shut down. The SIGTERM
may come at any time, even while the work is still running, and the plugin must appropriately shut down. If the work is not complete, it is important that the plugin does not send error output to STDOUT. If the plugin fails to stop by itself within 30 seconds, the plugin container is forcefully stopped.
"},{"location":"arcaflow/contributing/plugin-protocol/#protocol","title":"Protocol","text":"As a data transport protocol, we use CBOR messages RFC 8949 back to back due to their self-delimiting nature. This section provides the entire protocol as JSON schema below.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-0-the-start-output-message","title":"Step 0: The \u201cstart output\u201d message","text":"Because Kubernetes has no clean way of capturing an output right at the start, the initial step of the plugin execution involves the engine sending an empty CBOR message (None
or Nil
) to the plugin. This indicates, that the plugin may start its output now.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-1-hello-message","title":"Step 1: Hello message","text":"The \u201cHello\u201d message is a way for the plugin to introduce itself and present its steps and schema. Transcribed to JSON, a message of this kind would look as follows:
{\n \"version\": 1,\n \"steps\": {\n \"step-id-1\": {\n \"name\": \"Step 1\",\n \"description\": \"This is the first step\",\n \"input\": {\n \"schema\": {\n // Input schema\n }\n },\n \"outputs\": {\n \"output-id-1\": {\n \"name\": \"Name for this output kind\",\n \"description\": \"Description for this output\",\n \"schema\": {\n // Output schema\n }\n }\n }\n }\n }\n}\n
The schemas must describe the data structure the plugin expects. For a simple hello world input would look as follows:
{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n }\n }\n}\n
The full schema is described below in the Schema section.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-2-start-work-message","title":"Step 2: Start work message","text":"The \u201cstart work\u201d message has the following parameters in CBOR:
{\n \"id\": \"id-of-the-step-to-execute\",\n \"config\": {\n // Input parameters according to schema here\n }\n}\n
The plugin must respond with a CBOR message of the following format:
{\n \"status\": \"started\"\n}\n
"},{"location":"arcaflow/contributing/plugin-protocol/#step-3a-crash","title":"Step 3/a: Crash","text":"If the plugin execution ended unexpectedly, the plugin should crash and output a reasonable error message to the standard error. The plugin must exit with a non-zero exit status to notify the engine that the execution failed.
"},{"location":"arcaflow/contributing/plugin-protocol/#step-3b-output","title":"Step 3/b: Output","text":"When the plugin has executed successfully, it must emit a CBOR message to the standard output:
{\n \"output_id\": \"id-of-the-declared-output\",\n \"output_data\": {\n // Result data of the plugin\n },\n \"debug_logs\": \"Unstructured logs here for debugging as a string.\"\n}\n
"},{"location":"arcaflow/contributing/plugin-protocol/#schema","title":"Schema","text":"This section contains the exact schema that the plugin sends to the engine.
Type: scope
Root object: Schema Properties steps (map[string, reference[Step]]
) Name: Steps Description: Steps this schema supports. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Step]
Referenced object: Step (see in the Objects section below) Objects AnySchema (object
) Type: object
Properties None
BoolSchema (object
) Type: object
Properties None
Display (object
) Type: object
Properties description (string
) Name: Description Description: Description for this item if needed. Required: No Minimum: 1 Examples \"Please select the fruit you would like.\"\n
icon (string
) Name: Icon Description: SVG icon for this item. Must have the declared size of 64x64, must not include additional namespaces, and must not reference external resources. Required: No Minimum: 1 Examples \"<svg ...></svg>\"\n
name (string
) Name: Name Description: Short text serving as a name or title for this item. Required: No Minimum: 1 Examples \"Fruit\"\n
Float (object
) Type: object
Properties max (float
) Name: Maximum Description: Maximum value for this float (inclusive). Required: No Examples 16.0\n
min (float
) Name: Minimum Description: Minimum value for this float (inclusive). Required: No Examples 5.0\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
Int (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum value for this int (inclusive). Required: No Minimum: 0 Examples 5\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
IntEnum (object
) Type: object
Properties units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
values (map[int, reference[Display]]
) Name: Values Description: Possible values for this field. Required: Yes | Minimum items: | 1 |\n
Key type Type: int
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\"1024\": {\"name\": \"kB\"}, \"1048576\": {\"name\": \"MB\"}}\n
List (object
) Type: object
Properties items (one of[string]
) Name: Items Description: ReflectedType definition for items in this list. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
Map (object
) Type: object
Properties keys (one of[string]
) Name: Keys Description: ReflectedType definition for keys in this map. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
values (one of[string]
) Name: Values Description: ReflectedType definition for values in this map. Required: No Object (object
) Type: object
Properties id (string
) Name: ID Description: Unique identifier for this object within the current scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
properties (map[string, reference[Property]]
) Name: Properties Description: Properties of this object. Required: Yes Key type Type: string
Minimum: 1 Value type Type: reference[Property]
Referenced object: Property (see in the Objects section below) OneOfIntSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[int, one of[string]]
) Name: Types Required: No Key type Type: int
Value type Type: one of[string]
OneOfStringSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[string, one of[string]]
) Name: Types Required: No Key type Type: string
Value type Type: one of[string]
Pattern (object
) Type: object
Properties None
Property (object
) Type: object
Properties conflicts (list[string]
) Name: Conflicts Description: The current property cannot be set if any of the listed properties are set. Required: No List Items Type: string
default (string
) Name: Default Description: Default value for this property in JSON encoding. The value must be unserializable by the type specified in the type field. Required: No display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) examples (list[string]
) Name: Examples Description: Example values for this property, encoded as JSON. Required: No List Items Type: string
required (bool
) Name: Required Description: When set to true, the value for this field must be provided under all circumstances. Required: No Defaulttrue\n
required_if (list[string]
) Name: Required if Description: Sets the current property to required if any of the properties in this list are set. Required: No List Items Type: string
required_if_not (list[string]
) Name: Required if not Description: Sets the current property to be required if none of the properties in this list are set. Required: No List Items Type: string
type (one of[string]
) Name: Type Description: Type definition for this field. Required: Yes Ref (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) id (string
) Name: ID Description: Referenced object ID. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Schema (object
) Type: object
Properties steps (map[string, reference[Step]]
) Name: Steps Description: Steps this schema supports. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Step]
Referenced object: Step (see in the Objects section below) Scope (object
) Type: object
Properties objects (map[string, reference[Object]]
) Name: Objects Description: A set of referencable objects. These objects may contain references themselves. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Object]
Referenced object: Object (see in the Objects section below) root (string
) Name: Root object Description: ID of the root object of the scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Step (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) id (string
) Name: ID Description: Machine identifier for this step. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
input (reference[Scope]
) Name: Input Description: Input data schema. Required: Yes Referenced object: Scope (see in the Objects section below) outputs (map[string, reference[StepOutput]]
) Name: Input Description: Input data schema. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[StepOutput]
Referenced object: StepOutput (see in the Objects section below) StepOutput (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) error (bool
) Name: Error Description: If set to true, this output will be treated as an error output. Required: No Defaultfalse\n
schema (reference[Scope]
) Name: Schema Description: Data schema for this particular output. Required: Yes Referenced object: Scope (see in the Objects section below) String (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 16\n
min (int
) Name: Minimum Description: Minimum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 5\n
pattern (pattern
) Name: Pattern Description: Regular expression this string must match. Required: No Examples \"^[a-zA-Z]+$\"\n
StringEnum (object
) Type: object
Properties values (map[string, reference[Display]]
) Name: Values Description: Mapping where the left side of the map holds the possible value and the right side holds the display value for forms, etc. Required: Yes | Minimum items: | 1 |\n
Key type Type: string
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\n \"apple\": {\n \"name\": \"Apple\"\n },\n \"orange\": {\n \"name\": \"Orange\"\n }\n}\n
Unit (object
) Type: object
Properties name_long_plural (string
) Name: Name long (plural) Description: Longer name for this UnitDefinition in plural form. Required: Yes Examples \"bytes\",\"characters\"\n
name_long_singular (string
) Name: Name long (singular) Description: Longer name for this UnitDefinition in singular form. Required: Yes Examples \"byte\",\"character\"\n
name_short_plural (string
) Name: Name short (plural) Description: Shorter name for this UnitDefinition in plural form. Required: Yes Examples \"B\",\"chars\"\n
name_short_singular (string
) Name: Name short (singular) Description: Shorter name for this UnitDefinition in singular form. Required: Yes Examples \"B\",\"char\"\n
Units (object
) Type: object
Properties base_unit (reference[Unit]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: Yes Referenced object: Unit (see in the Objects section below) Examples {\n \"name_short_singular\": \"B\",\n \"name_short_plural\": \"B\",\n \"name_long_singular\": \"byte\",\n \"name_long_plural\": \"bytes\"\n}\n
multipliers (map[int, reference[Unit]]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: No Key type Type: int
Value type Type: reference[Unit]
Referenced object: Unit (see in the Objects section below) Examples {\n \"1024\": {\n \"name_short_singular\": \"kB\",\n \"name_short_plural\": \"kB\",\n \"name_long_singular\": \"kilobyte\",\n \"name_long_plural\": \"kilobytes\"\n },\n \"1048576\": {\n \"name_short_singular\": \"MB\",\n \"name_short_plural\": \"MB\",\n \"name_long_singular\": \"megabyte\",\n \"name_long_plural\": \"megabytes\"\n }\n}\n
"},{"location":"arcaflow/contributing/typing/","title":"The Arcaflow type system","text":"Arcaflow takes a departure from the classic run-and-pray approach of running workloads and validates workflows before executing them. To do this, Arcaflow starts the plugins as needed before the workflow is run and queries them for their schema. This schema will contain information about what kind of input a plugin requests and what kind of outputs it can produce.
A plugin can support multiple workflow steps and must provide information about the data types in its input and output for each step. A step can have exactly one input format, but may declare more than one output.
The type system is inspired by JSON schema and OpenAPI, but it is more restrictive due to the need to efficiently serialize workloads over various formats.
"},{"location":"arcaflow/contributing/typing/#types","title":"Types","text":"The typing system supports the following data types.
- Objects are key-value pairs where the keys are always a fixed set of strings and values are of various types declared for each key. They are similar to classes in most programming languages. Fields in objects can be optional, which means they will have no value (commonly known as
null
, nil
, or None
), or a default value. - OneOf are a special type that is a union of multiple objects, distinguished by a special field called the discriminator.
- Lists are a sequence of values of the same type. The value type can be any of the other types described in this section. List items must always have a value and cannot be empty (
null
, nil
, or None
). - Maps are key-value pairs that always have fixed types for both keys and values. Maps with mixed keys or values are not supported. Map keys can only be strings, integers, or enums. Map keys and values must always have a value and cannot be empty (
null
, nil
, or None
). - Enums are either strings or integers that can take only a fixed set of values. Enums with mixed value types are not supported.
- Strings are a sequence of bytes.
- Patterns are regular expressions.
- Integers are 64-bit numbers that can take both positive and negative values.
- Floats are 64-bit floating point numbers that can take both positive and negative values.
- Booleans are values of
true
or false
and cannot take any other values. - Scopes and Refs are object-like types that allow you to create circular references (see below).
- Any accepts any primitive type (string, int, float, bool, map, list) but no patterns, objects, etc.
"},{"location":"arcaflow/contributing/typing/#validation","title":"Validation","text":"The typing system also contains more in-depth validation than just simple types:
"},{"location":"arcaflow/contributing/typing/#strings","title":"Strings","text":"Strings can have a minimum or maximum length, as well as validation against a regular expression.
"},{"location":"arcaflow/contributing/typing/#ints-floats","title":"Ints, floats","text":"Number types can have a minimum and maximum value (inclusive).
"},{"location":"arcaflow/contributing/typing/#booleans","title":"Booleans","text":"Boolean types can take a value of either true
or false
, but when unserializing from YAML or JSON formats, strings or int values of true
, yes
, on
, enable
, enabled
, 1
, false
, no
, off
, disable
, disabled
or 0
are also accepted.
"},{"location":"arcaflow/contributing/typing/#lists-maps","title":"Lists, maps","text":"Lists and maps can have constraints on the minimum or maximum number of items in them (inclusive).
"},{"location":"arcaflow/contributing/typing/#objects","title":"Objects","text":"Object fields can have several constraints:
required_if
has a list of other fields that, if set, make the current field required. required_if_not
has a list of other fields that, if none are set, make the current field required. conflicts
has a list of other fields that cannot be set together with the current field.
"},{"location":"arcaflow/contributing/typing/#oneof","title":"OneOf","text":"When you need to create a list of multiple object types, or simply have an either-or choice between two object types, you can use the OneOf type. This field uses an already existing field of the underlying objects, or adds an extra field to the schema to distinguish between the different types. Translated to JSON, you might see something like this:
{\n \"_type\": \"Greeter\",\n \"message\": \"Hello world!\"\n}\n
"},{"location":"arcaflow/contributing/typing/#scopes-and-refs","title":"Scopes and refs","text":"Objects, on their own, cannot create circular references. It is not possible to create two objects that refer to each other. That\u2019s where scopes and refs come into play. Scopes hold a list of objects, identified by an ID. Refs inside the scope (for example, in an object property) can refer to these IDs. Every scope has a root object, which will be used to provide its \u201cobject-like\u201d features, such as a list of fields.
For example:
objects:\n my_root_object:\n id: my_root_object\n properties:\n ...\nroot: my_root_object\n
Multiple scopes can be nested into each other. The ref always refers to the closest scope up the tree. Multiple scopes can be used when combining objects from several sources (e.g. several plugins) into one schema to avoid conflicting ID assignments.
"},{"location":"arcaflow/contributing/typing/#any","title":"Any","text":"Any accepts any primitive type (string, int, float, bool, map, list) but no patterns, objects, etc. This type is severely limited in its ability to validate data and should only be used in exceptional cases when there is no other way to describe a schema.
"},{"location":"arcaflow/contributing/typing/#metadata","title":"Metadata","text":"Object fields can also declare metadata that will help with creating user interfaces for the object. These fields are:
- name: A user-readable name for the field.
- description: A user-readable description for the field. It may contain newlines, but no other formatting is allowed.
- icon: SVG icon
"},{"location":"arcaflow/contributing/typing/#intent-inference","title":"Intent inference","text":"For display purposes, the type system is designed so that it can infer the intent of the data. We wish to communicate the following intents:
- Graphs are x-y values of timestamps mapped to one or more values.
- Log lines are timestamps associated with text.
- Events are timestamps associated with other structured data.
We explicitly document the following inference rules, which will probably change in the future.
- A map with keys of timestamps and values of integers or floats is rendered as a graph.
- A map with keys of timestamps and values of objects consisting only of integers and floats is rendered as a graph.
- A map with keys of timestamps and values of strings is considered a log line.
- A map with keys of timestamps and objects that don\u2019t match the rules above are considered an event.
- A map with keys of short strings and integer or float values is considered a pie chart.
- A list of objects consisting of a single timestamp and otherwise only integers and floats is rendered as a graph.
- A list of objects with a single timestamp and a single string are considered a log line.
- A list of objects with a single short string and a single integer or float is considered a pie chart.
- A list of objects consisting of no more than one timestamp and multiple other fields not matching the rules above is considered an event.
- If an object has a field called \u201ctitle\u201d, \u201cname\u201d, or \u201clabel\u201d, it will be used as a label for the current data set in a chart, or as a title for the wrapping box for the user interface elements.
"},{"location":"arcaflow/contributing/typing/#reference-manual","title":"Reference Manual","text":"This section explains how a scope object looks like. The plugin protocol contains a few more types that are used when communicating a schema.
Type: scope
Root object: Scope Properties objects (map[string, reference[Object]]
) Name: Objects Description: A set of referencable objects. These objects may contain references themselves. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Object]
Referenced object: Object (see in the Objects section below) root (string
) Name: Root object Description: ID of the root object of the scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Objects AnySchema (object
) Type: object
Properties None
BoolSchema (object
) Type: object
Properties None
Display (object
) Type: object
Properties description (string
) Name: Description Description: Description for this item if needed. Required: No Minimum: 1 Examples \"Please select the fruit you would like.\"\n
icon (string
) Name: Icon Description: SVG icon for this item. Must have the declared size of 64x64, must not include additional namespaces, and must not reference external resources. Required: No Minimum: 1 Examples \"<svg ...></svg>\"\n
name (string
) Name: Name Description: Short text serving as a name or title for this item. Required: No Minimum: 1 Examples \"Fruit\"\n
Float (object
) Type: object
Properties max (float
) Name: Maximum Description: Maximum value for this float (inclusive). Required: No Examples 16.0\n
min (float
) Name: Minimum Description: Minimum value for this float (inclusive). Required: No Examples 5.0\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
Int (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum value for this int (inclusive). Required: No Minimum: 0 Examples 5\n
units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
IntEnum (object
) Type: object
Properties units (reference[Units]
) Name: Units Description: Units this number represents. Required: No Referenced object: Units (see in the Objects section below) Examples { \"base_unit\": { \"name_short_singular\": \"%\", \"name_short_plural\": \"%\", \"name_long_singular\": \"percent\", \"name_long_plural\": \"percent\" }}\n
values (map[int, reference[Display]]
) Name: Values Description: Possible values for this field. Required: Yes | Minimum items: | 1 |\n
Key type Type: int
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\"1024\": {\"name\": \"kB\"}, \"1048576\": {\"name\": \"MB\"}}\n
List (object
) Type: object
Properties items (one of[string]
) Name: Items Description: ReflectedType definition for items in this list. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
Map (object
) Type: object
Properties keys (one of[string]
) Name: Keys Description: ReflectedType definition for keys in this map. Required: No max (int
) Name: Maximum Description: Maximum value for this int (inclusive). Required: No Minimum: 0 Examples 16\n
min (int
) Name: Minimum Description: Minimum number of items in this list.. Required: No Minimum: 0 Examples 5\n
values (one of[string]
) Name: Values Description: ReflectedType definition for values in this map. Required: No Object (object
) Type: object
Properties id (string
) Name: ID Description: Unique identifier for this object within the current scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
properties (map[string, reference[Property]]
) Name: Properties Description: Properties of this object. Required: Yes Key type Type: string
Minimum: 1 Value type Type: reference[Property]
Referenced object: Property (see in the Objects section below) OneOfIntSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[int, one of[string]]
) Name: Types Required: No Key type Type: int
Value type Type: one of[string]
OneOfStringSchema (object
) Type: object
Properties discriminator_field_name (string
) Name: Discriminator field name Description: Name of the field used to discriminate between possible values. If this field is present on any of the component objects it must also be an int. Required: No Examples \"_type\"\n
types (map[string, one of[string]]
) Name: Types Required: No Key type Type: string
Value type Type: one of[string]
Pattern (object
) Type: object
Properties None
Property (object
) Type: object
Properties conflicts (list[string]
) Name: Conflicts Description: The current property cannot be set if any of the listed properties are set. Required: No List Items Type: string
default (string
) Name: Default Description: Default value for this property in JSON encoding. The value must be unserializable by the type specified in the type field. Required: No display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) examples (list[string]
) Name: Examples Description: Example values for this property, encoded as JSON. Required: No List Items Type: string
required (bool
) Name: Required Description: When set to true, the value for this field must be provided under all circumstances. Required: No Defaulttrue\n
required_if (list[string]
) Name: Required if Description: Sets the current property to required if any of the properties in this list are set. Required: No List Items Type: string
required_if_not (list[string]
) Name: Required if not Description: Sets the current property to be required if none of the properties in this list are set. Required: No List Items Type: string
type (one of[string]
) Name: Type Description: Type definition for this field. Required: Yes Ref (object
) Type: object
Properties display (reference[Display]
) Name: Display Description: Name, description and icon. Required: No Referenced object: Display (see in the Objects section below) id (string
) Name: ID Description: Referenced object ID. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Scope (object
) Type: object
Properties objects (map[string, reference[Object]]
) Name: Objects Description: A set of referencable objects. These objects may contain references themselves. Required: Yes Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
Value type Type: reference[Object]
Referenced object: Object (see in the Objects section below) root (string
) Name: Root object Description: ID of the root object of the scope. Required: Yes Minimum: 1 Maximum: 255 Must match pattern: ^[$@a-zA-Z0-9-_]+$
String (object
) Type: object
Properties max (int
) Name: Maximum Description: Maximum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 16\n
min (int
) Name: Minimum Description: Minimum length for this string (inclusive). Required: No Minimum: 0 Units: characters Examples 5\n
pattern (pattern
) Name: Pattern Description: Regular expression this string must match. Required: No Examples \"^[a-zA-Z]+$\"\n
StringEnum (object
) Type: object
Properties values (map[string, reference[Display]]
) Name: Values Description: Mapping where the left side of the map holds the possible value and the right side holds the display value for forms, etc. Required: Yes | Minimum items: | 1 |\n
Key type Type: string
Value type Type: reference[Display]
Referenced object: Display (see in the Objects section below) Examples {\n \"apple\": {\n \"name\": \"Apple\"\n },\n \"orange\": {\n \"name\": \"Orange\"\n }\n}\n
Unit (object
) Type: object
Properties name_long_plural (string
) Name: Name long (plural) Description: Longer name for this UnitDefinition in plural form. Required: Yes Examples \"bytes\",\"characters\"\n
name_long_singular (string
) Name: Name long (singular) Description: Longer name for this UnitDefinition in singular form. Required: Yes Examples \"byte\",\"character\"\n
name_short_plural (string
) Name: Name short (plural) Description: Shorter name for this UnitDefinition in plural form. Required: Yes Examples \"B\",\"chars\"\n
name_short_singular (string
) Name: Name short (singular) Description: Shorter name for this UnitDefinition in singular form. Required: Yes Examples \"B\",\"char\"\n
Units (object
) Type: object
Properties base_unit (reference[Unit]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: Yes Referenced object: Unit (see in the Objects section below) Examples {\n \"name_short_singular\": \"B\",\n \"name_short_plural\": \"B\",\n \"name_long_singular\": \"byte\",\n \"name_long_plural\": \"bytes\"\n}\n
multipliers (map[int, reference[Unit]]
) Name: Base UnitDefinition Description: The base UnitDefinition is the smallest UnitDefinition of scale for this set of UnitsDefinition. Required: No Key type Type: int
Value type Type: reference[Unit]
Referenced object: Unit (see in the Objects section below) Examples {\n \"1024\": {\n \"name_short_singular\": \"kB\",\n \"name_short_plural\": \"kB\",\n \"name_long_singular\": \"kilobyte\",\n \"name_long_plural\": \"kilobytes\"\n },\n \"1048576\": {\n \"name_short_singular\": \"MB\",\n \"name_short_plural\": \"MB\",\n \"name_long_singular\": \"megabyte\",\n \"name_long_plural\": \"megabytes\"\n }\n}\n
"},{"location":"arcaflow/plugins/","title":"Creating Arcaflow plugins","text":"Arcaflow supports writing plugins in any language, and we provide pre-made libraries for Python and Go.
Plugins in Arcaflow run in containers, so you can use dependencies and libraries.
Writing plugins in Python Python is the easiest language to start writing plugins in, you simply need to write a few dataclasses and a function and that\u2019s already a working plugin.
Read more about Python plugins \u00bb
Writing plugins in Go Go is the programming language of the engine. Writing plugins in Go is more complicated than Python because you will need to provide both the struct
s and the Arcaflow schema. We recommend Go for plugins that interact with Kubernetes.
Read more about Go plugins \u00bb
Packaging plugins To use plugins with Arcaflow, you will need to package them into a container image. You can, of course, write your own Dockerfile
, but we provide a handy utility called Carpenter to automate the process.
Read more about packaging \u00bb
"},{"location":"arcaflow/plugins/packaging/","title":"Packaging Arcaflow plugins","text":"Arcaflow plugins are distributed using container images. Whatever programming language you are using, you will need to package it up into a container image and distribute it via a container registry.
"},{"location":"arcaflow/plugins/packaging/#the-manual-method","title":"The manual method","text":"Currently, we only support the manual method for non-Arcalot plugins. However, it\u2019s very simple. First, create a Dockerfile for your programming language:
PythonGo With Python, the Dockerfile heavily depends on which build tool you are using. Here we are demonstrating the usage using pip.
FROM python:alpine\n\n# Add the plugin contents\nADD . /plugin\n# Set the working directory\nWORKDIR /plugin\n\n# Install the dependencies. Customize this\n# to your Python package manager.\nRUN pip install -r requirements.txt\n\n# Set this to your .py file\nENTRYPOINT [\"/usr/local/bin/python3\", /plugin/plugin.py\"]\n# Make sure this stays empty!\nCMD []\n
For Go plugins we recommend a multi-stage build so the source code doesn\u2019t unnecessarily bloat the image. (Keep in mind, for some libraries you will need to include at least a LICENSE and possibly a NOTICE file in the image.)
FROM golang AS build\n# Add the plugin contents\nADD . /plugin\n# Set the working directory\nWORKDIR /plugin\n# Build your image\nENV CGO_ENABLED=0\nRUN go build -o plugin\n\n# Start from an empty image\nFROM scratch\n# Copy the built binary\nCOPY --from=build /plugin/plugin /plugin\n# Set the entry point\nENTRYPOINT [\"/plugin\"]\n# Make sure this stays empty!\nCMD []\n
That\u2019s it! Now you can run your build:
docker build -t example.com/your-namespace/your-plugin:latest .\ndocker push example.com/your-namespace/your-plugin:latest\n
"},{"location":"arcaflow/plugins/go/","title":"Creating plugins in Go","text":"In contrast to Python, Go doesn\u2019t contain enough language elements to infer the types and validation from Go types. Therefore, in order to use Go you both need to create the data structures (e.g. struct
) and write the schema by hand. Therefore, we recommend Python for writing plugins.
For writing Go plugins, you will need:
- Go version 1.18 or higher.
- The Go SDK for Arcaflow plugins installed (preferably via go mod).
- A container engine that can build images for packaging.
If you have these three, you can get started with your first plugin.
"},{"location":"arcaflow/plugins/go/first/","title":"Writing your first Go plugin","text":"In order to create a Go plugin, you will need to create a Go module project (go mod init
) and install the Arcaflow SDK using go get go.flow.arcalot.io/pluginsdk
.
Writing a Go plugin consists of the following 4 parts:
- The input data model
- The output data model
- The callable function
- The calling scaffold
"},{"location":"arcaflow/plugins/go/first/#the-input-data-model","title":"The input data model","text":"First, we define an input data model. This must be a struct.
type Input struct {\n Name string `json:\"name\"`\n}\n
Note
The Arcaflow serialization does not use the built-in Go JSON marshaling, so any additional tags like omitempty
, or yaml
tags are ignored.
In addition to the struct above, we must also define a schema for the input data structure:
// We define a separate scope, so we can add sub-objects later.\nvar inputSchema = schema.NewScopeSchema(\n // Struct-mapped object schemas are object definitions that are mapped to a specific struct (Input)\n schema.NewStructMappedObjectSchema[Input](\n // ID for the object:\n \"input\",\n // Properties of the object:\n map[string]*schema.PropertySchema{\n \"name\": schema.NewPropertySchema(\n // Type properties:\n schema.NewStringSchema(nil, nil, nil),\n // Display metadata:\n schema.NewDisplayValue(\n schema.PointerTo(\"Name\"),\n schema.PointerTo(\"Name of the person to greet.\"),\n nil,\n ),\n // Required:\n true,\n // Required if:\n []string{},\n // Required if not:\n []string{},\n // Conflicts:\n []string{},\n // Default value, JSON encoded:\n nil,\n //Examples:\n nil,\n )\n },\n ),\n)\n
"},{"location":"arcaflow/plugins/go/first/#the-output-data-model","title":"The output data model","text":"The output data model is similar to the input. First, we define our output struct:
type Output struct {\n Message string `json:\"message\"`\n}\n
Then, we have to describe the schema for this output similar to the input:
var outputSchema = schema.NewScopeSchema(\n schema.NewStructMappedObjectSchema[Output](\n \"output\",\n map[string]*schema.PropertySchema{\n \"message\": schema.NewPropertySchema(\n schema.NewStringSchema(nil, nil, nil),\n schema.NewDisplayValue(\n schema.PointerTo(\"Message\"),\n schema.PointerTo(\"The resulting message.\"),\n nil,\n ),\n true,\n nil,\n nil,\n nil,\n nil,\n nil,\n )\n },\n ),\n)\n
"},{"location":"arcaflow/plugins/go/first/#the-callable-function","title":"The callable function","text":"Now we can create a callable function. This function will always take one input and produce an output ID (e.g. \"success\"
) and an output data structure. This allows you to return one of multiple possible outputs.
func greet(input Input) (string, any) {\n return \"success\", Output{\n fmt.Sprintf(\"Hello, %s!\", input.Name), \n }\n}\n
Finally, we can incorporate this function into a step schema:
var greetSchema = schema.NewCallableSchema(\n schema.NewCallableStep[Input](\n // ID of the function:\n \"greet\",\n // Add the input schema:\n inputSchema,\n map[string]*schema.StepOutputSchema{\n // Define possible outputs:\n \"success\": schema.NewStepOutputSchema(\n // Add the output schema:\n outputSchema,\n schema.NewDisplayValue(\n schema.PointerTo(\"Success\"),\n schema.PointerTo(\"Successfully created message.\"),\n nil,\n ),\n false,\n ),\n },\n // Metadata for the function:\n schema.NewDisplayValue(\n schema.PointerTo(\"Greet\"),\n schema.PointerTo(\"Greets the user.\"),\n nil,\n ),\n // Reference the function\n greet,\n )\n)\n
"},{"location":"arcaflow/plugins/go/first/#the-calling-scaffold","title":"The calling scaffold","text":"Finally, we need to create our main function to run the plugin:
package main\n\nimport (\n \"go.flow.arcalot.io/pluginsdk/plugin\"\n)\n\nfunc main() {\n plugin.Run(greetSchema)\n}\n
"},{"location":"arcaflow/plugins/go/first/#running-the-plugin","title":"Running the plugin","text":"Go plugins currently cannot run as CLI tools, so you will have to use this plugin in conjunction with the Arcaflow Engine. However, you can dump the schema by running:
go run yourplugin.go --schema\n
"},{"location":"arcaflow/plugins/go/first/#next-steps","title":"Next steps","text":"Once you are finished with your first plugin, you should read the section about writing a schema.
"},{"location":"arcaflow/plugins/go/schema/","title":"Writing a schema in Go","text":"In contrast to Python, the Go SDK does not have the ability to infer the schema from the code of a plugin. The Go language does not have enough information to provide enough information.
Therefore, schemas in Go need to be written by hand. This document will explain the details and intricacies of writing a Go schema by hand.
"},{"location":"arcaflow/plugins/go/schema/#typed-vs-untyped-serialization","title":"Typed vs. untyped serialization","text":"Since Go is a strongly and statically typed language, there are two ways to serialize and unserialize a type.
The untyped serialization functions (Serialize
, Unserialize
) always result in an any
type (interface{}
for pre-1.18 code) and you will have to perform a type assertion to get the type you can actually work with.
The typed serialization functions (SerializeType
, UnserializeType
) result in a specific type, but cannot be used in lists, maps, etc. due to the lack of language features, such as covariance.
In practice, you will always use untyped functions when writing a plugin, typed functions are only useful for writing Arcaflow Engine code.
"},{"location":"arcaflow/plugins/go/schema/#strings","title":"Strings","text":"You can define a string by calling schema.NewStringSchema()
. It has 3 parameters:
- The minimum number of characters in the string. (
*int64
) - The maximum number of characters in the string. (
*int64
) - A regular expression the string must match. (
*regexp.Regexp
)
It will result in a *StringSchema
, which also complies with the schema.String
interface. It unserializes from a string, integer, float to a string and serializes back to a string.
Tip
You can easily convert a value to a pointer by using the schema.PointerTo()
function.
"},{"location":"arcaflow/plugins/go/schema/#patterns","title":"Patterns","text":"You can define a regular expression pattern by calling schema.NewPatternSchema()
. It has no parameters and will result in a *PatternSchema
, which also complies with the schema.Pattern
interface. It unserializes from a string to a *regexp.Regexp
and serializes back to a string.
"},{"location":"arcaflow/plugins/go/schema/#integer","title":"Integer","text":"Integers in Are always 64-bit signed integers. You can define an integer type with the schema.NewIntSchema()
function. It takes the following parameters:
- The minimum value for the integer. (
*int64
) - The maximum value for the integer. (
*int64
) - The units of the integer. (
*UnitsDefinition
, see Units)
When unserializing from a string, or another int or float type, the SDK will attempt to parse it as an integer. When serializing, the integer type will always be serialized as an integer.
"},{"location":"arcaflow/plugins/go/schema/#floating-point-numbers","title":"Floating point numbers","text":"Floating point numbers are always stored as 64-bit floating point numbers. You can define a float type with the schema.NewFloatSchema()
function. It takes the following parameters:
- The minimum value for the float. (
*float64
) - The maximum value for the float. (
*float64
) - The units of the float. (
*UnitsDefinition
, see Units)
When unserializing from a string, or another int or float type, the SDK will attempt to parse it as a float. When serializing, the float type will always be serialized as a float.
"},{"location":"arcaflow/plugins/go/schema/#booleans","title":"Booleans","text":"You can define a boolean by calling schema.NewBoolSchema()
. It has no parameters and will result in a *BoolSchema
, which also complies with the schema.Bool
interface.
It converts both integers and strings to boolean if possible. The following values are accepted as true
or false
, respectively:
1
yes
y
on
true
enable
enabled
0
no
n
off
false
disable
disabled
Boolean types will always serialize to bool
.
"},{"location":"arcaflow/plugins/go/schema/#enums","title":"Enums","text":"Go doesn\u2019t have any built-in enums, so Arcaflow supports int64
and string
-based enums. You can define an int enum by calling the schema.NewIntEnumSchema()
function. It takes the following parameters:
- A
map[int64]*DisplayValue
of values. The keys are the valid values in the enum. The values are display values, which can also be nil if no special display properties are desired. - The units of the enum. (
*UnitsDefinition
, see Units)
Strings can be defined by using the schema.NewStringEnumSchema()
function, which only takes the first parameter with string
keys.
Both functions return a *EnumSchema[string|int64]
, which also complies with the Enum[string|int64]
interface.
"},{"location":"arcaflow/plugins/go/schema/#lists","title":"Lists","text":"Lists come in two variants: typed and untyped. (See Typed vs. Untyped.) You can create an untyped list by calling schema.NewListSchema()
and a typed list by calling schema.NewTypedListSchema()
. Both have the following parameters:
- The type of item in the list. For untyped lists, this is a plain schema, for typed lists this must also be a typed schema.
- The minimum number of items in the list. (
*int64
) - The maximum number of items in the list. (
*int64
)
The result is a *ListSchema
for untyped lists, and a *TypedListSchema
for typed lists, which also satisfy their corresponding interfaces.
"},{"location":"arcaflow/plugins/go/schema/#maps","title":"Maps","text":"Maps, like lists, come in two variants: typed and untyped. (See Typed vs. Untyped.) You can create an untyped map by calling schema.NewMapSchema()
and a typed map by calling schema.NewTypedMapSchema()
. They both have the following parameters:
- The key type. This must be a schema of
string
, int
, or an enum thereof. - The value type. This can be any schema.
- The minimum number of items in the map. (
*int64
) - The maximum number of items in the map. (
*int64
)
The functions return a *schema.MapSchema
and *schema.TypedMapSchema
, respectively, which satisfy their corresponding interfaces.
"},{"location":"arcaflow/plugins/go/schema/#objects","title":"Objects","text":"Objects come in not two, but three variants: untyped, struct-mapped, and typed. (See Typed vs. Untyped.) Untyped objects unserialize to a map[string]any
, whereas struct-mapped objects are bound to a struct, but behave like untyped objects. Typed objects are bound to a struct and are typed. In plugins, you will always want to use struct-mapped object schemas.
You can create objects with the following functions:
schema.NewObjectSchema
for untyped objects. schema.NewStructMappedObjectSchema
for struct-mapped objects. schema.NewTypedObject
for typed objects.
They all have two parameters:
- A unique object identifier in the current scope. (See Scopes.)
- A map of string to PropertySchema objects describing the object properties.
"},{"location":"arcaflow/plugins/go/schema/#properties","title":"Properties","text":"Properties of objects are always untyped. You can create a property by calling schema.NewPropertySchema()
and it has the following parameters:
- The underlying type for the property.
- The display options for this property. (See Display values.)
- If the property is required. (
bool
) - The required-if fields. If any of these fields in the current object is set, the current property also becomes required. (
[]string
) - The required-if-not fields. If none of these fields are set in the current object, the current property becomes required. (
[]string
) - The fields the current field conflicts with. If any of these fields are set, the current field must not be set. (
[]string
) - The default value for the current property. (JSON-serialized
*string
) - Examples for the current property. (JSON-serialized
[]string
)
"},{"location":"arcaflow/plugins/go/schema/#scopes","title":"Scopes","text":"Sometimes, objects need to have circular references to each other. That\u2019s where scopes help. Scopes behave like objects, but act as a container for Refs. They contain a root object and additional objects that can be referenced by ID.
You can create a scope by calling schema.NewScopeSchema()
. It takes the following parameters:
- The root object.
- A list of additional objects that can be referenced by ID.
Warning
When using scopes, you must call ApplyScope
on the outermost scope once you have constructed your type tree, otherwise references won\u2019t work.
"},{"location":"arcaflow/plugins/go/schema/#refs","title":"Refs","text":"Refs are references to objects in the current scope. You can create a ref by calling schema.NewRefSchema()
. It takes two parameters:
- The ID of the object referenced.
- The display properties of this reference. (See Display values.)
"},{"location":"arcaflow/plugins/go/schema/#one-of","title":"One-of","text":"Sometimes, a field must be able to hold more than one type of item. That\u2019s where one-of types come into play. They behave like objects, but have a special field called the discriminator which differentiates between the different possible types. This discriminator field can either be an integer or a string.
You can use schema.NewOneOfIntSchema()
to create an integer-based one-of type and schema.NewOneOfStringSchema()
to create a string-based one. They both accept two parameters:
- A
map[int64|string]Object
, which holds the discriminator values and their corresponding objects (these can be refs or scopes too). - A
string
holding the name of the discriminator field.
The objects in the map are allowed to skip the discriminator field, but if they use it, it must have the same type as listed here.
"},{"location":"arcaflow/plugins/go/schema/#any","title":"Any","text":"The \u201cany\u201d type allows any primitive type to pass through. However, this comes with severe limitations and the data cannot be validated, so its use is discouraged. You can create an AnySchema
by calling schema.NewAnySchema()
. This function has no parameters.
"},{"location":"arcaflow/plugins/go/schema/#display-values","title":"Display values","text":"Several types, for example properties, accept a display value. This is a value designed to be rendered as a form field. It has three parameters:
- A short, human-readable name.
- A longer, possibly multi-line description.
- An embedded SVG icon. This icon should be 64x64 pixels and not contain any external references (e.g. CSS.)
Display types are always optional (can be nil
) and you can create one by calling schema.NewDisplayValue()
"},{"location":"arcaflow/plugins/go/schema/#units","title":"Units","text":"Units make it easier to parse and display numeric values. For example, if you have an integer representing nanoseconds, you may want to parse strings like 5m30s
. This is similar to the duration type in Go, but with the capabilities of defining your own units.
Units have two parameters: the base type and multipliers. You can define a unit type by calling schema.NewUnits()
and provide the base unit and multipliers by calling schema.NewUnit()
.
var u = schema.NewUnits(\n // Base unit:\n NewUnit(\n // Short name, singular\n \"B\",\n // Short name, plural\n \"B\",\n // Long name, singular\n \"byte\",\n // Long name, plural\n \"bytes\",\n ),\n // Multipliers\n map[int64]*UnitDefinition{\n 1024: NewUnit(\n \"kB\",\n \"kB\",\n \"kilobyte\",\n \"kilobytes\",\n ),\n //...\n },\n)\n
You can use the built-in schema.UnitBytes
, schema.UnitDurationNanoseconds
, and schema.UnitDurationSeconds
units for your plugins.
"},{"location":"arcaflow/plugins/python/","title":"Creating plugins with Python","text":"If you want to create an Arcaflow plugin in Python, you will need three things:
- Python 3.9+ (PyPy is supported)
- The Python SDK for Arcaflow plugins
- A container engine that can build images for packaging.
If you have these three, you can get started with your first plugin.
"},{"location":"arcaflow/plugins/python/data-model/","title":"Creating a Python data model","text":"Every plugin needs a schema to represent its expected inputs and outputs in a machine-readable format. The schema strong typing is a core design element of Arcaflow, enabling us to build portable workflows that compartmentalize failure conditions and avoid data errors.
When creating a data model for Arcaflow plugins in Python, everything starts with dataclasses. They allow Arcaflow to get information about the data types of individual fields in your class:
plugin.pyimport dataclasses\n\n\n@dataclasses.dataclass\nclass MyDataModel:\n some_field: str\n other_field: int\n
However, Arcaflow doesn\u2019t support all Python data types. You pick from the following list:
str
int
float
bool
- Enums
re.Pattern
typing.List[othertype]
typing.Dict[keytype, valuetype]
typing.Union[onedataclass, anotherdataclass]
- Dataclasses
typing.Any
You can read more about the individual types in the data types section
"},{"location":"arcaflow/plugins/python/data-model/#optional-parameters","title":"Optional parameters","text":"You can also declare any parameter as optional, like this:
plugin.py@dataclasses.dataclass\nclass MyClass:\n param: typing.Optional[int] = None\n
Note that adding typing.Optional
is not enough, you must specify the default value.
"},{"location":"arcaflow/plugins/python/data-model/#annotations-for-validation-and-metadata","title":"Annotations for validation and metadata","text":"You can specify desired validations for each field like this:
plugin.py@dataclasses.dataclass\nclass MyClass:\n param: typing.Annotated[int, schema.name(\"Param\")]\n
Tip
Annotated objects are preferred as a best practice for a documented schema, and are expected for any officially-supported community plugins.
You can use the following annotations to add metadata to your fields:
schema.id
adds a serialized field name for the current field (e.g. one containing dashes, which is not valid in Python) schema.name
adds a human-readable name to the parameter. This can be used to present a form field. schema.description
adds a long-form description to the field. schema.example
adds an example value to the field. You can repeat this annotation multiple times. The example must be provided as primitive types (no dataclasses).
You can also add validations to the fields. The following annotations are valid for all data types:
schema.required_if
specifies a field that causes the current field to be required. If the other field is empty, the current field is not required. You can repeat this annotation multiple times. (Make sure to use the optional annotation above.) schema.required_if_not
specifies a field that, if not filled, causes the current field to be required. You can repeat this annotation multiple times.(Make sure to use the optional annotation above.) schema.conflicts
specifies a field that cannot be used together with the current field. You can repeat this annotation multiple times. (Make sure to use the optional annotation above.)
Additionally, some data types have their own validations and metadata, such as schema.min
, schema.max
, schema.pattern
, or schema.units
.
Note
When combining typing.Annotated
with typing.Optional
, the default value is assigned to the Annotated
object, not to the Optional
object.
plugin.py@dataclasses.dataclass\nclass MyClass:\n param: typing.Annotated[\n typing.Optional[int],\n schema.name(\"Param\")\n ] = None\n
"},{"location":"arcaflow/plugins/python/data-model/#data-types","title":"Data types","text":""},{"location":"arcaflow/plugins/python/data-model/#strings","title":"Strings","text":"Strings are, as the name suggests, strings of human-readable characters. You can specify them in your dataclass like this:
some_field: str\n
Additionally, you can apply the following validations:
schema.min()
specifies the minimum length of the string if the field is set. schema.max()
specifies the maximum length of the string if the field is set. schema.pattern()
specifies the regular expression the string must match if the field is set.
"},{"location":"arcaflow/plugins/python/data-model/#integers","title":"Integers","text":"Integers are 64-bit signed whole numbers. You can specify them in your dataclass like this:
some_field: int\n
Additionally, you can apply the following validations and metadata:
schema.min()
specifies the minimum number if the field is set. schema.max()
specifies the maximum number if the field is set. schema.units()
specifies the units for this field (e.g. bytes). See Units.
"},{"location":"arcaflow/plugins/python/data-model/#floating-point-numbers","title":"Floating point numbers","text":"Floating point numbers are 64-bit signed fractions. You can specify them in your dataclass like this:
some_field: float\n
Warning
Floating point numbers are inaccurate! Make sure to transmit numbers requiring accuracy as integers!
Additionally, you can apply the following validations and metadata:
schema.min()
specifies the minimum number if the field is set. schema.max()
specifies the maximum number if the field is set. schema.units()
specifies the units for this field (e.g. bytes). See Units.
"},{"location":"arcaflow/plugins/python/data-model/#booleans","title":"Booleans","text":"Booleans are True
or False
values. You can specify them in your dataclass like this:
some_field: bool\n
Booleans have no additional validations or metadata.
"},{"location":"arcaflow/plugins/python/data-model/#enums","title":"Enums","text":"Enums, short for enumerations, are used to define a set of named values as unique constants. They provide a way to represent a fixed number of possible values for a variable, parameter, or property. In Python, an enum is declared as a class, but doesn\u2019t behave as a normal class. Instead, the \u201cattributes\u201d of the class act as independent \u201cmember\u201d or \u201cenumeration member\u201d objects, each of which has a name and a constant value.
By using enums, you can give meaningful names to distinct values, making the code more self-explanatory and providing a convenient way to work with sets of related constants.
In an Arcaflow schema, an Enum type provides a list of valid values for a field. The Enum must define a set of members with unique values, all of which are either strings or integers.
You can specify an enum with string values like this:
import enum\n\n\nclass MyEnum(enum.Enum):\n Value1 = \"value 1\"\n Value2 = \"value 2\"\n\nmy_field: MyEnum\n
The MyEnum class above defines two members, Value1 and Value2. Each member is associated with a constant value, in this case, the strings \u201cvalue 1\u201d and \u201cvalue 2\u201d respectively. An input value of \u201cvalue 1\u201d will result in the plugin seeing a value for my_field
of MyEnum.Value1. You can specify an Enum class with integer values like this:
import enum\n\nclass MyEnum(enum.Enum):\n Value1 = 1\n Value2 = 2\n\nmy_field: MyEnum\n
The my_field
variable is a variable of type MyEnum. It can store one of the defined enumeration members (Value1 or Value2). An input value of 1 in this case will result in the plugin receiving a value for my_field
of MyEnum.Value1.
value = MyEnum.Value1\n
In the above example, the Value1 member of MyEnum is accessed and assigned to the variable value. Note
Enumeration members are \u201csingleton\u201d objects which have a single instance. In Python, you should compare enumeration members using is
rather than ==
(for example, variable is MyEnum.Value1
). The values of an Enum used in an Arcaflow schema must have values of string or integer data type.
Tip
Enums aren\u2019t dataclasses, but can be used as the type of dataclass attributes.
Warning
Do not mix integers and strings in the same enum! The values for each Enum type must all be strings, or all integers.
"},{"location":"arcaflow/plugins/python/data-model/#patterns","title":"Patterns","text":"When you need to hold regular expressions, you can use a pattern field. This is tied to the Python regular expressions library. You can specify a pattern field like this:
import re\n\nmy_field: re.Pattern\n
Pattern fields have no additional validations or metadata.
Note
If you are looking for a way to do pattern/regex matching for a string you will need to use the schema.pattern() validation which specifies the regular expression, to which the string must match.
The below example declares that the first_name variable must only have uppercase and lowercase alphabets.
plugin.py@dataclasses.dataclass\nclass MyClass:\n first_name: typing.Annotated[\n str,\n schema.min(2),\n schema.pattern(re.compile(\"^[a-zA-Z]+$\")),\n schema.example(\"Arca\"),\n schema.name(\"First name\")\n ]\n
"},{"location":"arcaflow/plugins/python/data-model/#lists","title":"Lists","text":"When you want to make a list in Arcaflow, you always need to specify its contents. You can do that like this:
my_field: typing.List[str]\n
Lists can have the following validations:
schema.min()
specifies the minimum number of items in the list. schema.max()
specifies the maximum number of items in the list.
Tip
Items in lists can also be annotated with validations.
"},{"location":"arcaflow/plugins/python/data-model/#dicts","title":"Dicts","text":"Dicts (maps in Arcaflow) are key-value pairs. You need to specify both the key and the value type. You can do that as follows:
my_field: typing.Dict[str, str]\n
Lists can have the following validations:
schema.min()
specifies the minimum number of items in the list. schema.max()
specifies the maximum number of items in the list.
Tip
Items in dicts can also be annotated with validations.
"},{"location":"arcaflow/plugins/python/data-model/#union-types","title":"Union types","text":"Union types (one-of in Arcaflow) allow you to specify two or more possible objects (dataclasses) that can be in a specific place. The only requirement is that there must be a common field (discriminator) and each dataclass must have a unique value for this field. If you do not add this field to your dataclasses, it will be added automatically for you.
For example:
import typing\nimport dataclasses\n\n\n@dataclasses.dataclass\nclass FullName:\n first_name: str\n last_name: str\n\n\n@dataclasses.dataclass\nclass Nickname:\n nickname: str\n\n\nname: typing.Annotated[\n typing.Union[\n typing.Annotated[FullName, schema.discriminator_value(\"full\")],\n typing.Annotated[Nickname, schema.discriminator_value(\"nick\")]\n ], schema.discriminator(\"name_type\")]\n
Tip
The schema.discriminator
and schema.discriminator_value
annotations are optional. If you do not specify them, a discriminator will be generated for you.
"},{"location":"arcaflow/plugins/python/data-model/#any-types","title":"Any types","text":"Any types allow you to pass through any primitive data (no dataclasses). However, this comes with severe limitations as far as validation and use in workflows is concerned, so this type should only be used in limited cases. For example, if you would like to create a plugin that inserts data into an ElasticSearch database the \u201cany\u201d type would be appropriate here.
You can define an \u201cany\u201d type like this:
my_data: typing.Any\n
"},{"location":"arcaflow/plugins/python/data-model/#units","title":"Units","text":"Integers and floats can have unit metadata associated with them. For example, a field may contain a unit description like this:
time: typing.Annotated[int, schema.units(schema.UNIT_TIME)]\n
In this case, a string like 5m30s
will automatically be parsed into nanoseconds. Integers will pass through without conversion. You can also define your own unit types. At minimum, you need to specify the base type (nanoseconds in this case), and you can specify multipliers:
my_units = schema.Units(\n schema.Unit(\n # Short, singular\n \"ns\",\n # Short, plural\n \"ns\",\n # Long, singular\n \"nanosecond\",\n # Long, plural\n \"nanoseconds\"\n ),\n {\n 1000: schema.Unit(\n \"ms\",\n \"ms\",\n \"microsecond\",\n \"microseconds\"\n ),\n # ...\n }\n)\n
You can then use this description in your schema.units
annotations. Additionally, you can also use it to convert an integer or float into its string representation with the my_units.format_short
or my_units.format_long
functions. If you need to parse a string yourself, you can use my_units.parse
.
"},{"location":"arcaflow/plugins/python/data-model/#built-in-units","title":"Built-In Units","text":"A number of unit types are built-in to the python SDK for convenience:
UNIT_BYTE
- Bytes and 2^10 multiples (kilo-, mega-, giga-, tera-, peta-) UNIT_TIME
- Nanoseconds and human-friendly multiples (microseconds, seconds, minutes, hours, days) UNIT_CHARACTER
- Character notations (char, chars, character, characters) UNIT_PERCENT
- Percentage notations (%, percent)
"},{"location":"arcaflow/plugins/python/embedding/","title":"Embedding your Python plugin","text":"Instead of using your plugin as a standalone tool or in conjunction with Arcaflow, you can also embed your plugin into your existing Python application. To do that you simply build a schema and then call the schema yourself. You can pass raw data as an input, and you\u2019ll get the benefit of schema validation.
myapp.pyfrom arcaflow_plugin_sdk import plugin\nimport my_arcaflow_plugin\n\n# Build your schema with the step functions passed\nschema = plugin.build_schema(my_arcaflow_plugin.hello_world)\n\n# Which step from the plugin we want to execute\nstep_id = \"hello_world\"\n\n# Input parameters. Note, these must be a dict, not a dataclass\nstep_params = {\n \"name\": \"Arca Lot\",\n}\n\n# Execute the step\noutput_id, output_data = schema(step_id, step_params)\n\n# Print which kind of result we have\npprint.pprint(output_id)\n\n# Print the result data\npprint.pprint(output_data)\n
However, the example above requires you to provide the data as a dict
, not a dataclass
, and it will also return a dict
as an output object. Sometimes, you may want to use a partial approach, where you only use part of the SDK. In this case, you can change your code to run any of the following functions, in order:
serialization.load_from_file()
to load a YAML or JSON file into a dict yourschema.unserialize_input()
to turn a dict
into a dataclass
needed for your steps yourschema.call_step()
to run a step with the unserialized dataclass
yourschema.serialize_output()
to turn the output dataclass
into a dict
"},{"location":"arcaflow/plugins/python/faq/","title":"Python SDK FAQ","text":""},{"location":"arcaflow/plugins/python/faq/#how-can-i-add-a-field-with-dashes-such-as-my-field","title":"How can I add a field with dashes, such as my-field
?","text":"Dataclasses don\u2019t support dashes in parameters. You can work around this by defining the id
annotation:
@dataclasses.dataclass\nclass MyData:\n my_field: typing.Annotated[\n str,\n schema.id(\"my-field\"),\n ]\n
"},{"location":"arcaflow/plugins/python/faq/#how-can-i-write-a-dataclass-from-a-schema-to-a-yaml-or-json-file","title":"How can I write a dataclass from a schema to a YAML or JSON file?","text":"You can extend Python\u2019s JSON encoder to support dataclasses. If that doesn\u2019t suit your needs, you can use this SDK to convert the dataclasses to their basic representations and then write that to your JSON or YAML file. First, add this outside of your step:
my_object_schema = plugin.build_object_schema(MyDataclass)\n
Inside your step function you can then dump the data from your input
def my_step(params: MyParams):\n yaml_contents = yaml.dump(my_object_schema.serialize(params.some_param))\n
"},{"location":"arcaflow/plugins/python/faq/#how-can-i-easily-load-a-list-from-a-yaml-or-json-into-a-list-of-dataclasses","title":"How can I easily load a list from a YAML or JSON into a list of dataclasses?","text":"This requires a bit of trickery. First, we build a schema from the dataclass representing the row or entry in the list:
my_row_schema = plugin.build_object_schema(MyRow)\n
Then you can create a list schema:
my_list_schema = schema.ListType(my_row_schema)\n
You can now unserialize a list obtained from the YAML or JSON file:
my_data = my_list_schema.unserialize(json.loads(...))\n
"},{"location":"arcaflow/plugins/python/first/","title":"Writing your first Python plugin","text":"In this guide you will learn how to write a basic \u201cHello World\u201d plugin for Arcaflow and then run it without the engine as a standalone tool. In order to proceed this tutorial, you will need to install Python version 3.9 or higher on your machine. The tutorial will make use of the Arcaflow Python SDK to provide the required functionality.
"},{"location":"arcaflow/plugins/python/first/#step-1-setting-up-your-environment","title":"Step 1: Setting up your environment","text":"If you have Python installed, you will need to set up your environment. You can use any dependency manager you like, but here are three methods to get you started quickly.
Official plugins
If you wish to contribute an official Arcaflow plugin on GitHub, please use Poetry. For simplicity, we only accept Poetry plugins.
From the template repositoryUsing pipUsing Poetry - Clone or download the template repository
- Figure out what the right command to call your Python version is:
python3.10 --version\npython3.9 --version\npython3 --version\npython --version\n
Make sure you have at least Python 3.9. - Create a virtualenv in your project directory using the following command, replacing your Python call:
python -m venv venv\n
- Activate the venv:
source venv/bin/activate\n
- Install the dependencies:
pip install -r requirements.txt\n
- Create an empty folder.
- Create a
requirements.txt
with the following content: arcaflow-plugin-sdk\n
- Figure out what the right command to call your Python version is:
python3.10 --version\npython3.9 --version\npython3 --version\npython --version\n
Make sure you have at least Python 3.9. - Create a virtualenv in your project directory using the following command, replacing your Python call:
python -m venv venv\n
- Activate the venv:
source venv/bin/activate\n
- Install the dependencies:
pip install -r requirements.txt\n
- Copy the example plugin, example config and the [tests]https://github.com/arcalot/arcaflow-plugin-template-python/blob/main/tests/test_example_plugin.py) to your directory.
- Assuming you have Poetry installed, run the following command:
poetry new your-plugin\n
Then change the current directory to your-plugin
. - Figure out what the right command to call your Python version is:
which python3.10\nwhich python3.9\nwhich python3\nwhich python\n
Make sure you have at least Python 3.9. - Set Poetry to Python 3.9:
poetry env use /path/to/your/python3.9\n
- Check that your
pyproject.toml
file has the following lines: [tool.poetry.dependencies]\npython = \"^3.9\"\n
- Add the SDK as a dependency:
poetry add arcaflow-plugin-sdk\n
- Copy the example plugin, example config and the [tests]https://github.com/arcalot/arcaflow-plugin-template-python/blob/main/tests/test_example_plugin.py) to your directory.
- Activate the venv:
poetry shell\n
Now you are ready to start hacking away at your plugin! You can open the example_plugin.py
file and follow along, or you can create a new Python file and write the code.
"},{"location":"arcaflow/plugins/python/first/#step-2-creating-an-input-and-output-data-model","title":"Step 2: Creating an input and output data model","text":"Plugins in Arcaflow must explain how they want their input data and what kind of output they produce. Let\u2019s start with the input data model. In our case, we want to ask the user for a name. Normally, you would write this in Python:
plugin.pydef hello_world(name):\n return f\"Hello, {name}\"\n
However, that\u2019s not how the Arcaflow SDK works. You must always specify the data type of any variable. Additionally, every function can only have one input, and it must be a dataclass.
So, let\u2019s change the code a little:
plugin.pyimport dataclasses\n\n\n@dataclasses.dataclass\nclass InputParams:\n name: str\n\ndef hello_world(params: InputParams):\n # ...\n
So far so good, but we are not done yet. The output also has special rules. One plugin function can have more than one possible output, so you need to say which output it is, and you need to also return a dataclass.
For example:
plugin.pyimport dataclasses\n\n\n@dataclasses.dataclass\nclass InputParams:\n name: str\n\n\n@dataclasses.dataclass\nclass SuccessOutput:\n message: str\n\n\ndef hello_world(params: InputParams):\n return \"success\", SuccessOutput(f\"Hello, {params.name}\")\n
Tip
If your plugin has a problem, you could create and return an ErrorOutput
instead. In the Arcaflow workflow you can then handle each output separately.
"},{"location":"arcaflow/plugins/python/first/#step-3-decorating-your-step-function","title":"Step 3: Decorating your step function","text":"Of course, Arcaflow doesn\u2019t know what to do with this code yet. You will need to add a decorator to the hello_world
function in order to give Arcaflow the necessary information:
plugin.pyfrom arcaflow_plugin_sdk import plugin\n\n\n@plugin.step(\n id=\"hello-world\",\n name=\"Hello world!\",\n description=\"Says hello :)\",\n outputs={\"success\": SuccessOutput},\n)\ndef hello_world(params: InputParams):\n # ...\n
Let\u2019s go through the parameters:
id
provides the step identifier. If your plugin provides more than one step function, you need to specify this in your workflow. name
provides the human-readable name of the plugin step. This will help render a user interface for the workflow. description
is a longer description for the function and may contain line breaks. outputs
specifies the possible outputs and the dataclasses associated with these outputs. This is important so Arcaflow knows what to expect.
Tip
If you want, you can specify the function return type like this, but Arcaflow won\u2019t use it:
def hello_world(params: InputParams) -> typing.Tuple[str, ...]:\n
Unfortunately, Python doesn\u2019t give us a good way to extract this information, so it\u2019s safe to skip."},{"location":"arcaflow/plugins/python/first/#step-4-running-the-plugin","title":"Step 4: Running the plugin","text":"There is one more piece missing to run a plugin: the calling code. Add the following to your file:
plugin.pyimport sys\nfrom arcaflow_plugin_sdk import plugin\n\n\nif __name__ == \"__main__\":\n sys.exit(\n plugin.run(\n plugin.build_schema(\n # List your step functions here:\n hello_world,\n )\n )\n )\n
Now your plugin is ready. You can package it up for a workflow, or you can run it as a standalone tool from the command line:
python example_plugin.py -f input-data.yaml\n
You will need to provide the input data in YAML format:
input-data.yamlname: Arca Lot\n
Tip
If your plugin provides more than one step function, you can specify the correct one to use with the -s
parameter.
Tip
To prevent output from breaking the functionality when attached to the Arcaflow Engine, the SDK hides any output your step function writes to the standard output or standard error. You can use the --debug
flag to show any output on the standard error in standalone mode.
Tip
You can generate a JSON schema file for your step input by running
python example_plugin.py --json-schema input >example.schema.json\n
If you are using the YAML plugin for VSCode, add the following line to the top of your input file for code completion:
# yaml-language-server: $schema=example.schema.json\n
"},{"location":"arcaflow/plugins/python/first/#next-steps","title":"Next steps","text":"In order to create an actually useful plugin, you will want to create a data model for your plugin. Once the data model is complete, you should look into packaging your plugin.
"},{"location":"arcaflow/plugins/python/official/","title":"Creating official Arcaflow plugins","text":"Official Arcaflow plugins have more stringent requirements than normal. This document describes how to create a plugin that conforms to those requirements.
"},{"location":"arcaflow/plugins/python/official/#development-environment","title":"Development environment","text":"Official Python plugins are standardized on Poetry and a Linux-based development environment.
"},{"location":"arcaflow/plugins/python/official/#installing-poetry","title":"Installing Poetry","text":"First, please ensure your python3
executable is at least version 3.9.
$ python3 --version\nPython 3.9.15\n
How to install Python RHEL, CentOS, FedoraUbuntu $ dnf -y install python3.9\n
$ apt-get -y install python3.9\n
Tip
If the python3
command doesn\u2019t work for you, but python3.9
does, you can alias the command:
$ alias python3=\"python3.9\"\n
Install Poetry using one of their supported methods for your environment.
Warning
Make sure to install Poetry into exactly one Python executable on your system. If something goes wrong with your package\u2019s Python virtual environment, you do not want to also spend time figuring out which Poetry executable is responsible for it.
Now, verify your Poetry version.
$ poetry --version\nPoetry (version 1.2.2)\n
"},{"location":"arcaflow/plugins/python/official/#setting-up-your-project","title":"Setting up your project","text":"Create your plugin project, plugin-project
, and change directory into the project root. You should see a directory structure similar to this with the following files.
$ poetry new plugin-project\nCreated package plugin_project in plugin-project\n\n$ tree plugin-project\nplugin-project\n\u251c\u2500\u2500 plugin_project\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 README.md\n\u2514\u2500\u2500 tests\n \u2514\u2500\u2500 __init__.py\n\n2 directories, 4 files\n\n$ cd plugin-project\n
Ensure python3
is at least 3.9.
$ python3 --version\nPython 3.9.15\n
Set Poetry to use your Python that is at least 3.9.
$ poetry env use $(which python3)\n
Check that your pyproject.toml
is using at least Python 3.9 by looking for the following line.
pyproject.toml[tool.poetry.dependencies]\npython = \"^3.9\"\n
Add the arcaflow plugin sdk for python as a software dependency for your Python project.
$ poetry add arcaflow-plugin-sdk-python\n
You should now have a poetry.lock
file in your project root. Poetry maintains the state of your pyproject.toml
, and its exact software dependencies as hashes in the poetry.lock
file.
"},{"location":"arcaflow/plugins/python/official/#building-a-container-image","title":"Building a container image","text":"To build an official plugin container image we use the carpenter workflow on GitHub Actions. This workflow calls the Arcaflow image builder to build the image and perform all validations necessary.
In order to successfully run the build, you should add the following files from the template repository:
Dockerfile
LICENSE
.flake8
Additionally, you need to add tests to your project, write a README.md
, and make sure that the code directory matches your project name.
"},{"location":"arcaflow/plugins/python/official/#publishing-on-pypi","title":"Publishing on PyPI","text":"Some plugins work well as libraries too. You can publish Arcaflow plugins on PyPI.
To push an official package to PyPI, please contact an Arcalot chair to create an API token on PyPI and set up a CI environment. For testing purposes you can use TestPyPI.
You can configure Poetry to use this API token by calling:
$ poetry config pypi-token.<any name> <PYPI API TOKEN>\n
Alternatively, you can also use environment variables:
$ export POETRY_PYPI_TOKEN_PYPI=my-token\n$ export POETRY_HTTP_BASIC_PYPI_USERNAME=<username>\n$ export POETRY_HTTP_BASIC_PYPI_PASSWORD=<password>\n
You can generate distribution archives by typing:
$ poetry build\n
You can then test publishing:
$ poetry publish --dry-run\n\nPublishing arcaflow-plugin-template-python (0.1.0) to PyPI\n- Uploading arcaflow_plugin_template_python-0.1.0-py3-none-any.whl 100%\n- Uploading arcaflow_plugin_template_python-0.1.0.tar.gz 100%\n
Remove the --dry-run
to actually publish or call poetry publish --build
to run it in one go.
"},{"location":"arcaflow/plugins/python/schema/","title":"Writing a Python plugin schema by hand","text":"If you want to skip the automatic schema generation described in previous chapters, you can also create a schema by hand.
Warning
This process is complicated, requires providing redundant information and should be avoided if at all possible. We recommend creating a data model using dataclasses, decorators and annotations.
We start by defining a schema:
from arcaflow_plugin_sdk import schema\nfrom typing import Dict\n\nsteps: Dict[str, schema.StepSchema]\n\ns = schema.Schema(\n steps,\n)\n
The steps
parameter here must be a dict, where the key is the step ID and the value is the step schema. So, let\u2019s create a step schema:
from arcaflow_plugin_sdk import schema\n\nstep_schema = schema.StepSchema(\n id = \"pod\",\n name = \"Pod scenario\",\n description = \"Kills pods\",\n input = input_schema,\n outputs = outputs,\n handler = my_handler_func\n)\n
Let\u2019s go in order:
- The
input
must be a schema of the type schema.ObjectType
. This describes the single parameter that will be passed to my_handler_func
. - The
outputs
describe a Dict[str, schema.ObjectType]
, where the key is the ID for the returned output type, while the value describes the output schema. - The
handler
function takes one parameter, the object described in input
and must return a tuple of a string and the output object. Here the ID uniquely identifies which output is intended, for example success
and error
, while the second parameter in the tuple must match the outputs
declaration.
That\u2019s it! Now all that\u2019s left is to define the ObjectType
and any sub-objects.
"},{"location":"arcaflow/plugins/python/schema/#objecttype","title":"ObjectType","text":"The ObjectType is intended as a backing type for dataclasses. For example:
t = schema.ObjectType(\n TestClass,\n {\n \"a\": schema.Field(\n type=schema.StringType(),\n required=True,\n ),\n \"b\": schema.Field(\n type=schema.IntType(),\n required=True,\n )\n }\n)\n
The fields support the following parameters:
type
: underlying type schema for the field (required) name
: name for the current field description
: description for the current field required
: marks the field as required required_if
: a list of other fields that, if filled, will also cause the current field to be required required_if_not
: a list of other fields that, if not set, will cause the current field to be required conflicts
: a list of other fields that cannot be set together with the current field
"},{"location":"arcaflow/plugins/python/schema/#scopetype-and-reftype","title":"ScopeType and RefType","text":"Sometimes it is necessary to create circular references. This is where the ScopeType
and the RefType
comes into play. Scopes contain a list of objects that can be referenced by their ID, but one object is special: the root object of the scope. The RefType, on the other hand, is there to reference objects in a scope.
Currently, the Python implementation passes the scope to the ref type directly, but the important rule is that ref types always reference their nearest scope up the tree. Do not create references that aim at scopes not directly above the ref!
For example:
@dataclasses.dataclass\nclass OneOfData1:\n a: str\n\n@dataclasses.dataclass\nclass OneOfData2:\n b: OneOfData1\n\nscope = schema.ScopeType(\n {\n \"OneOfData1\": schema.ObjectType(\n OneOfData1,\n {\n \"a\": schema.Field(\n schema.StringType()\n )\n }\n ),\n },\n # Root object of scopes\n \"OneOfData2\",\n)\n\nscope.objects[\"OneOfData2\"] = schema.ObjectType(\n OneOfData2,\n {\n \"b\": schema.Field(\n schema.RefType(\"OneOfData1\", scope)\n )\n }\n)\n
As you can see, this API is not easy to use and is likely to change in the future.
"},{"location":"arcaflow/plugins/python/schema/#oneoftype","title":"OneOfType","text":"The OneOfType allows you to create a type that is a combination of other ObjectTypes. When a value is deserialized, a special discriminator field is consulted to figure out which type is actually being sent.
This discriminator field may be present in the underlying type. If it is, the type must match the declaration in the AnyOfType.
For example:
@dataclasses.dataclass\nclass OneOfData1:\n type: str\n a: str\n\n@dataclasses.dataclass\nclass OneOfData2:\n b: int\n\nscope = schema.ScopeType(\n {\n \"OneOfData1\": schema.ObjectType(\n OneOfData1,\n {\n # Here the discriminator field is also present in the underlying type\n \"type\": schema.Field(\n schema.StringType(),\n ),\n \"a\": schema.Field(\n schema.StringType()\n )\n }\n ),\n \"OneOfData2\": schema.ObjectType(\n OneOfData2,\n {\n \"b\": schema.Field(\n schema.IntType()\n )\n }\n )\n },\n # Root object of scopes\n \"OneOfData1\",\n)\n\ns = schema.OneOfStringType(\n {\n # Option 1\n \"a\": schema.RefType(\n # The RefType resolves against the scope.\n \"OneOfData1\",\n scope\n ),\n # Option 2\n \"b\": schema.RefType(\n \"OneOfData2\",\n scope\n ),\n },\n # Pass the scope this type belongs do\n scope,\n # Discriminator field\n \"type\",\n)\n\nserialized_data = s.serialize(OneOfData1(\n \"a\",\n \"Hello world!\"\n))\npprint.pprint(serialized_data)\n
Note, that the OneOfTypes take all object-like elements, such as refs, objects, or scopes.
"},{"location":"arcaflow/plugins/python/schema/#stringtype","title":"StringType","text":"String types indicate that the underlying type is a string.
t = schema.StringType()\n
The string type supports the following parameters:
min_length
: minimum length for the string (inclusive) max_length
: maximum length for the string (inclusive) pattern
: regular expression the string must match
"},{"location":"arcaflow/plugins/python/schema/#patterntype","title":"PatternType","text":"The pattern type indicates that the field must contain a regular expression. It will be decoded as re.Pattern
.
t = schema.PatternType()\n
The pattern type has no parameters.
"},{"location":"arcaflow/plugins/python/schema/#inttype","title":"IntType","text":"The int type indicates that the underlying type is an integer.
t = schema.IntType()\n
The int type supports the following parameters:
min
: minimum value for the number (inclusive). max
: minimum value for the number (inclusive).
"},{"location":"arcaflow/plugins/python/schema/#floattype","title":"FloatType","text":"The float type indicates that the underlying type is a floating point number.
t = schema.FloatType()\n
The float type supports the following parameters:
min
: minimum value for the number (inclusive). max
: minimum value for the number (inclusive).
"},{"location":"arcaflow/plugins/python/schema/#booltype","title":"BoolType","text":"The bool type indicates that the underlying value is a boolean. When unserializing, this type also supports string and integer values of true
, yes
, on
, enable
, enabled
, 1
, false
, no
, off
, disable
, disabled
or 0
.
"},{"location":"arcaflow/plugins/python/schema/#enumtype","title":"EnumType","text":"The enum type creates a type from an existing enum:
class MyEnum(Enum):\n A = \"a\"\n B = \"b\"\n\nt = schema.EnumType(MyEnum)\n
The enum type has no further parameters.
"},{"location":"arcaflow/plugins/python/schema/#listtype","title":"ListType","text":"The list type describes a list of items. The item type must be described:
t = schema.ListType(\n schema.StringType()\n)\n
The list type supports the following extra parameters:
min
: The minimum number of items in the list (inclusive) max
: The maximum number of items in the list (inclusive)
"},{"location":"arcaflow/plugins/python/schema/#maptype","title":"MapType","text":"The map type describes a key-value type (dict). You must specify both the key and the value type:
t = schema.MapType(\n schema.StringType(),\n schema.StringType()\n)\n
The map type supports the following extra parameters:
min
: The minimum number of items in the map (inclusive) max
: The maximum number of items in the map (inclusive)
"},{"location":"arcaflow/plugins/python/schema/#anytype","title":"AnyType","text":"The \u201cany\u201d type allows any primitive type to pass through. However, this comes with severe limitations and the data cannot be validated, so its use is discouraged. You can create an AnyType
by simply doing this:
t = schema.AnyType()\n
"},{"location":"arcaflow/plugins/python/schema/#running-the-plugin","title":"Running the plugin","text":"If you create the schema by hand, you can add the following code to your plugin:
if __name__ == \"__main__\":\n sys.exit(plugin.run(your_schema))\n
You can then run your plugin as described in the writing your first plugin section.
"},{"location":"arcaflow/plugins/python/testing/","title":"Testing your Python plugin","text":"When writing your first plugin, you will probably want to test it manually. However, as development progresses, you should switch to automated testing. Automated testing makes sure your plugins don\u2019t break when you introduce changes.
This page describes the following test scenarios:
- Manual testing helps
- Serialization tests for your input and output to make sure your classes can be serialized for transport
- Functional tests that call your plugin and make sure it works correctly
"},{"location":"arcaflow/plugins/python/testing/#manual-testing","title":"Manual testing","text":"Manual testing is easy: prepare a test input file in YAML format, then run the plugin as a command line tool. For example, the hello world plugin would take this input:
name: Arca Lot\n
You could then run the example plugin:
python example_plugin -f my-input-file.yaml\n
The plugin will run and present you with the output.
Tip
If you have more than one step, don\u2019t forget to pass the -s step-id
parameter.
Tip
To prevent output from breaking the functionality when attached to the Arcaflow Engine, the SDK hides any output your step function writes to the standard output or standard error. You can use the --debug
flag to show any output on the standard error in standalone mode.
"},{"location":"arcaflow/plugins/python/testing/#writing-a-serialization-test","title":"Writing a serialization test","text":"You can use any test framework you like for your serialization test, we\u2019ll demonstrate with unittest as it is included directly in Python. The key to this test is to call plugin.test_object_serialization()
with an instance of your dataclass that you want to test:
class ExamplePluginTest(unittest.TestCase):\n def test_serialization(self):\n self.assertTrue(plugin.test_object_serialization(\n example_plugin.PodScenarioResults(\n [\n example_plugin.Pod(\n namespace=\"default\",\n name=\"nginx-asdf\"\n )\n ]\n )\n ))\n
Remember, you need to call this function with an instance containing actual data, not just the class name.
The test function will first serialize, then unserialize your data and check if it\u2019s the same. If you want to use a manually created schema, you can do so, too:
class ExamplePluginTest(unittest.TestCase):\n def test_serialization(self):\n plugin.test_object_serialization(\n example_plugin.PodScenarioResults(\n #...\n ),\n schema.ObjectType(\n #...\n )\n )\n
"},{"location":"arcaflow/plugins/python/testing/#functional-tests","title":"Functional tests","text":"Functional tests don\u2019t have anything special about them. You can directly call your code with your dataclasses as parameters, and check the return. This works best on auto-generated schemas with the @plugin.step
decorator. See below for manually created schemas.
class ExamplePluginTest(unittest.TestCase):\n def test_functional(self):\n input = example_plugin.PodScenarioParams()\n\n output_id, output_data = example_plugin.pod_scenario(input)\n\n # Check if the output is always an error, as it is the case for the example plugin.\n self.assertEqual(\"error\", output_id)\n self.assertEqual(\n output_data,\n example_plugin.PodScenarioError(\n \"Cannot kill pod .* in namespace .*, function not implemented\"\n )\n )\n
If you created your schema manually, the best way to write your tests is to include the schema in your test. This will automatically validate both the input and the output, making sure they conform to your schema. For example:
class ExamplePluginTest(unittest.TestCase):\n def test_functional(self):\n step_schema = schema.StepSchema(\n #...\n handler = example_plugin.pod_scenario,\n )\n input = example_plugin.PodScenarioParams()\n\n output_id, output_data = step_schema(input)\n\n # Check if the output is always an error, as it is the case for the example plugin.\n self.assertEqual(\"error\", output_id)\n self.assertEqual(\n output_data,\n example_plugin.PodScenarioError(\n \"Cannot kill pod .* in namespace .*, function not implemented\"\n )\n )\n
"},{"location":"arcaflow/running/","title":"Running Arcaflow","text":"Running Arcaflow is simple! You will need three things:
- A local container engine (e.g. Docker or Podman)
- The Arcaflow Engine
- A workflow file (see the example workflows)
Please start by setting up Arcaflow.
"},{"location":"arcaflow/running/running/","title":"Running Arcaflow","text":"Before you proceed, you will need to perform the following steps:
- Download and configure Arcaflow
- Create a YAML file with your workflow input data (e.g. `input.yaml)
Linux/MacOSWindows /path/to/arcaflow -input path/to/input.yaml\n
c:\\path\\to\\arcaflow.exe -input path/to/input.yaml\n
You can pass the following additional options to Arcaflow:
Option Description -config /path/to/config.yaml
Set an Arcaflow configuration file. (See the configuration guide.) -context /path/to/workflow/dir
Set a different workflow directory. (Defaults to the current directory.) -workflow workflow.yaml
Set a different workflow file. (Defaults to workflow.yaml
.)"},{"location":"arcaflow/running/running/#execution","title":"Execution","text":"Once you start Arcaflow, it will perform the following three phases:
- It will start all plugins using your local deployer (see the configuration guide), load their schemas, and then stop the plugins.
Note
The loading phase only reads the plugin schemas; it does not run any of the functional steps of the plugins.
- It will execute the workflow.
- Once the workflow execution is complete, it will output the resulting data.
Tip
You can redirect the standard output to capture the output data and still read the log messages on the standard error.
"},{"location":"arcaflow/running/setup/","title":"Setting up Arcaflow","text":"In order to use Arcaflow, you will need to download the Arcaflow Engine. You can simply unpack and run it, no need for installing it.
On Linux and macOS, you may need to run chmod +x
on the engine binary.
"},{"location":"arcaflow/running/setup/#configuration","title":"Configuration","text":"If you are using Docker as the local deployer (see below), you generally do not need to perform any extra configuration.
If you wish to customize Arcaflow, you can pass a YAML configuration file to Arcaflow with the -config your-arcaflow-config.yaml
parameter.
"},{"location":"arcaflow/running/setup/#local-deployer","title":"Local deployer","text":"The Arcaflow Engine needs a local container deployer to temporarily run plugins and read their schema. We recommend either Docker (default) or Podman for this purpose. You can use a Kubernetes cluster for this purpose too, but a local container engine is the better choice for performance reasons.
You can then change the deployer type like this:
config.yamldeployer:\n type: podman\n # Deployer-specific options \n
DockerPodmanKubernetes Docker is the default local deployer. You can configure it like this:
config.yamldeployer:\n type: docker\n connection:\n # Change this to point to a TCP-based Docker socket\n host: host-to-docker \n # Add a certificates here. This is usually needed in TCP mode.\n cacert: |\n Add your CA cert PEM here\n cert: |\n Add your client cert PEM here.\n key: |\n Add your client key PEM here.\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n network:\n # Add your network config here\n platform:\n # Add your platform config here\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Docker deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Connection (object
) Type: object
Properties cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify the Dockerd server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name for Dockerd. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z0-9./:_-]+$
Default\"npipe:////./pipe/docker_engine\"\n
Examples 'unix:///var/run/docker.sock'\n
\u201d
'npipe:////./pipe/docker_engine'\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (map[string, string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[A-Z0-9_]+$
Value type Type: string
Maximum: 32760 Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
network (reference[NetworkConfig]
) Name: Network configuration Description: Provides information about the container networking for the plugin. Required: No Referenced object: NetworkConfig (see in the Objects section below) platform (reference[PlatformConfig]
) Name: Platform configuration Description: Provides information about the container host platform for the plugin. Required: No Referenced object: PlatformConfig (see in the Objects section below) HostConfig (object
) Type: object
Properties CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) NetworkConfig (object
) Type: object
Properties None
PlatformConfig (object
) Type: object
Properties None
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
If you want to use Podman as your local deployer instead of Docker, you can do so like this:
config.yamldeployer:\n type: podman\n podman:\n # Change where Podman is. (You can use this to point to a shell script\n path: /path/to/your/podman\n # Change the network mode\n networkMode: host\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Podman deployer Type: scope
Root object: Config Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) Objects Config (object
) Type: object
Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (list[string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+=.+$
Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
HostConfig (object
) Type: object
Properties Binds (list[string]
) Name: Volume Bindings Description: Volumes Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+:.+$
CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) Podman (object
) Type: object
Properties cgroupNs (string
) Name: CGroup namespace Description: Provides the Cgroup Namespace settings for the container Required: No Must match pattern: ^host|ns:/proc/\\d+/ns/cgroup|container:.+|private$
containerName (string
) Name: Container Name Description: Provides name of the container Required: No Must match pattern: ^.*$
imageArchitecture (string
) Name: Podman image Architecture Description: Provides Podman Image Architecture Required: No Must match pattern: ^.*$
Default\"amd64\"\n
imageOS (string
) Name: Podman Image OS Description: Provides Podman Image Operating System Required: No Must match pattern: ^.*$
Default\"linux\"\n
networkMode (string
) Name: Network Mode Description: Provides network settings for the container Required: No Must match pattern: ^bridge:.*|host|none$
path (string
) Name: Podman path Description: Provides the path of podman executable Required: No Must match pattern: ^.*$
Default\"podman\"\n
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
Kubernetes can be used as the \u201clocal\u201d deployer, but this is typically not recommended for performance reasons. You can set up the Kubernetes deployer like this:
config.yamldeployer:\n type: kubernetes\n connection:\n host: localhost:6443\n cert: |\n Add your client cert in PEM format here.\n key: |\n Add your client key in PEM format here.\n cacert: |\n Add the server CA cert in PEM format here.\n
All options for the Kubernetes deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects AWSElasticBlockStoreVolumeSource (object
) Type: object
Properties None
AzureDiskVolumeSource (object
) Type: object
Properties None
AzureFileVolumeSource (object
) Type: object
Properties None
CSIVolumeSource (object
) Type: object
Properties None
CephFSVolumeSource (object
) Type: object
Properties None
CinderVolumeSource (object
) Type: object
Properties None
Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) ConfigMapVolumeSource (object
) Type: object
Properties None
Connection (object
) Type: object
Properties bearerToken (string
) Name: Bearer token Description: Bearer token to authenticate against the Kubernetes API with. Required: No burst (int
) Name: Burst Description: Burst value for query throttling. Required: No Minimum: 0 Default10\n
cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify Kubernetes server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name and port of the Kubernetes server Required: No Default\"kubernetes.default.svc\"\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
password (string
) Name: Password Description: Password for basic authentication. Required: No path (string
) Name: Path Description: Path to the API server. Required: No Default\"/api\"\n
qps (float
) Name: QPS Description: Queries Per Second allowed against the API. Required: No Minimum: 0 Units: queries Default5.0\n
serverName (string
) Name: TLS server name Description: Expected TLS server name to verify in the certificate. Required: No username (string
) Name: Username Description: Username for basic authentication. Required: No Container (object
) Type: object
Properties args (list[string]
) Name: Arguments Description: Arguments to the entypoint (command). Required: No List Items Type: string
command (list[string]
) Name: Command Description: Override container entry point. Not executed with a shell. Required: No Minimum items: 1 List Items Type: string
env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) image (string
) Name: Image Description: Container image to use for this container. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9_\\-:./]+$
imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 workingDir (string
) Name: Working directory Description: Override the container working directory. Required: No DownwardAPIVolumeSource (object
) Type: object
Properties None
EmptyDirVolumeSource (object
) Type: object
Properties medium (string
) Name: Medium Description: How to store the empty directory Required: No Minimum: 1 Must match pattern: ^(|Memory|HugePages|HugePages-.*)$
EnvFromSource (object
) Type: object
Properties configMapRef (object
) Name: Config map source Description: Populates the source from a config map. Required: No Properties name (string
) Name: Name Description: Name of the referenced config map. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the config map must be defined. Required: No prefix (string
) Name: Prefix Description: An optional identifier to prepend to each key in the ConfigMap. Required: No Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
secretRef (object
) Name: Secret source Description: Populates the source from a secret. Required: No Properties name (string
) Name: Name Description: Name of the referenced secret. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the secret must be defined. Required: No EphemeralVolumeSource (object
) Type: object
Properties None
FCVolumeSource (object
) Type: object
Properties None
FlexVolumeSource (object
) Type: object
Properties None
FlockerVolumeSource (object
) Type: object
Properties None
GCEPersistentDiskVolumeSource (object
) Type: object
Properties None
GlusterfsVolumeSource (object
) Type: object
Properties None
HostPathVolumeSource (object
) Type: object
Properties path (string
) Name: Path Description: Path to the directory on the host. Required: Yes Minimum: 1 Examples \"/srv/volume1\"\n
type (enum[string]
) Name: Type Description: Type of the host path. Required: No Values - `` Unset
BlockDevice
Block device CharDevice
Character device Directory
Directory DirectoryOrCreate
Create directory if not found File
File FileOrCreate
Create file if not found Socket
Socket
ISCSIVolumeSource (object
) Type: object
Properties None
NFSVolumeSource (object
) Type: object
Properties None
ObjectMeta (object
) Type: object
Properties annotations (map[string, string]
) Name: Annotations Description: Kubernetes annotations to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
generateName (string
) Name: Name prefix Description: Name prefix to generate pod names from. Required: No labels (map[string, string]
) Name: Labels Description: Kubernetes labels to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
name (string
) Name: Name Description: Pod name. Required: No namespace (string
) Name: Namespace Description: Kubernetes namespace to deploy in. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"default\"\n
PersistentVolumeClaimVolumeSource (object
) Type: object
Properties None
PhotonPersistentDiskVolumeSource (object
) Type: object
Properties None
Pod (object
) Type: object
Properties metadata (reference[ObjectMeta]
) Name: Metadata Description: Pod metadata. Required: No Referenced object: ObjectMeta (see in the Objects section below) spec (reference[PodSpec]
) Name: Specification Description: Pod specification. Required: No Referenced object: PodSpec (see in the Objects section below) PodSpec (object
) Type: object
Properties affinity (object
) Name: Affinity rules Description: Affinity rules. Required: No Properties podAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
podAntiAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
containers (list[reference[Container]]
) Name: Containers Description: A list of containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) initContainers (list[reference[Container]]
) Name: Init containers Description: A list of initialization containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) nodeSelector (map[string, string]
) Name: Labels Description: Node labels you want the target node to have. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
pluginContainer (object
) Name: Plugin container Description: The container to run the plugin in. Required: Yes Properties env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"arcaflow-plugin-container\"\n
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 volumes (list[reference[Volume]]
) Name: Volumes Description: A list of volumes that can be mounted by containers belonging to the pod. Required: No List Items Type: reference[Volume]
Referenced object: Volume (see in the Objects section below) PortworxVolumeSource (object
) Type: object
Properties None
ProjectedVolumeSource (object
) Type: object
Properties None
QuobyteVolumeSource (object
) Type: object
Properties None
RBDVolumeSource (object
) Type: object
Properties None
ScaleIOVolumeSource (object
) Type: object
Properties None
SecretVolumeSource (object
) Type: object
Properties None
StorageOSVolumeSource (object
) Type: object
Properties None
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
Volume (object
) Type: object
Properties awsElasticBlockStore (reference[AWSElasticBlockStoreVolumeSource]
) Name: AWS EBS Description: AWS Elastic Block Storage. Required: No Referenced object: AWSElasticBlockStoreVolumeSource (see in the Objects section below) azureDisk (reference[AzureDiskVolumeSource]
) Name: Azure Data Disk Description: Mount an Azure Data Disk as a volume. Required: No Referenced object: AzureDiskVolumeSource (see in the Objects section below) azureFile (reference[AzureFileVolumeSource]
) Name: Azure File Description: Mount an Azure File Service mount. Required: No Referenced object: AzureFileVolumeSource (see in the Objects section below) cephfs (reference[CephFSVolumeSource]
) Name: CephFS Description: Mount a CephFS volume. Required: No Referenced object: CephFSVolumeSource (see in the Objects section below) cinder (reference[CinderVolumeSource]
) Name: Cinder Description: Mount a cinder volume attached and mounted on the host machine. Required: No Referenced object: CinderVolumeSource (see in the Objects section below) configMap (reference[ConfigMapVolumeSource]
) Name: ConfigMap Description: Mount a ConfigMap as a volume. Required: No Referenced object: ConfigMapVolumeSource (see in the Objects section below) csi (reference[CSIVolumeSource]
) Name: CSI Volume Description: Mount a volume using a CSI driver. Required: No Referenced object: CSIVolumeSource (see in the Objects section below) downwardAPI (reference[DownwardAPIVolumeSource]
) Name: Downward API Description: Specify a volume that the pod should mount itself. Required: No Referenced object: DownwardAPIVolumeSource (see in the Objects section below) emptyDir (reference[EmptyDirVolumeSource]
) Name: Empty directory Description: Temporary empty directory. Required: No Referenced object: EmptyDirVolumeSource (see in the Objects section below) ephemeral (reference[EphemeralVolumeSource]
) Name: Ephemeral Description: Mount a volume that is handled by a cluster storage driver. Required: No Referenced object: EphemeralVolumeSource (see in the Objects section below) fc (reference[FCVolumeSource]
) Name: Fibre Channel Description: Mount a Fibre Channel volume that's attached to the host machine. Required: No Referenced object: FCVolumeSource (see in the Objects section below) flexVolume (reference[FlexVolumeSource]
) Name: Flex Description: Mount a generic volume provisioned/attached using an exec based plugin. Required: No Referenced object: FlexVolumeSource (see in the Objects section below) flocker (reference[FlockerVolumeSource]
) Name: Flocker Description: Mount a Flocker volume. Required: No Referenced object: FlockerVolumeSource (see in the Objects section below) gcePersistentDisk (reference[GCEPersistentDiskVolumeSource]
) Name: GCE disk Description: Google Cloud disk. Required: No Referenced object: GCEPersistentDiskVolumeSource (see in the Objects section below) glusterfs (reference[GlusterfsVolumeSource]
) Name: GlusterFS Description: Mount a Gluster volume. Required: No Referenced object: GlusterfsVolumeSource (see in the Objects section below) hostPath (reference[HostPathVolumeSource]
) Name: Host path Description: Mount volume from the host. Required: No Referenced object: HostPathVolumeSource (see in the Objects section below) iscsi (reference[ISCSIVolumeSource]
) Name: iSCSI Description: Mount an iSCSI volume. Required: No Referenced object: ISCSIVolumeSource (see in the Objects section below) name (string
) Name: Name Description: The name this volume can be referenced by. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
nfs (reference[NFSVolumeSource]
) Name: NFS Description: Mount an NFS share. Required: No Referenced object: NFSVolumeSource (see in the Objects section below) persistentVolumeClaim (reference[PersistentVolumeClaimVolumeSource]
) Name: Persistent Volume Claim Description: Mount a Persistent Volume Claim. Required: No Referenced object: PersistentVolumeClaimVolumeSource (see in the Objects section below) photonPersistentDisk (reference[PhotonPersistentDiskVolumeSource]
) Name: PhotonController persistent disk Description: Mount a PhotonController persistent disk as a volume. Required: No Referenced object: PhotonPersistentDiskVolumeSource (see in the Objects section below) portworxVolume (reference[PortworxVolumeSource]
) Name: Portworx Volume Description: Mount a Portworx volume. Required: No Referenced object: PortworxVolumeSource (see in the Objects section below) projected (reference[ProjectedVolumeSource]
) Name: Projected Description: Projected items for all in one resources secrets, configmaps, and downward API. Required: No Referenced object: ProjectedVolumeSource (see in the Objects section below) quobyte (reference[QuobyteVolumeSource]
) Name: quobyte Description: Mount Quobyte volume from the host. Required: No Referenced object: QuobyteVolumeSource (see in the Objects section below) rbd (reference[RBDVolumeSource]
) Name: Rados Block Device Description: Mount a Rados Block Device. Required: No Referenced object: RBDVolumeSource (see in the Objects section below) scaleIO (reference[ScaleIOVolumeSource]
) Name: ScaleIO Persistent Volume Description: Mount a ScaleIO persistent volume. Required: No Referenced object: ScaleIOVolumeSource (see in the Objects section below) secret (reference[SecretVolumeSource]
) Name: Secret Description: Mount a Kubernetes secret. Required: No Referenced object: SecretVolumeSource (see in the Objects section below) storageos (reference[StorageOSVolumeSource]
) Name: StorageOS Volume Description: Mount a StorageOS volume. Required: No Referenced object: StorageOSVolumeSource (see in the Objects section below) vsphereVolume (reference[VsphereVirtualDiskVolumeSource]
) Name: vSphere Virtual Disk Description: Mount a vSphere Virtual Disk as a volume. Required: No Referenced object: VsphereVirtualDiskVolumeSource (see in the Objects section below) VsphereVirtualDiskVolumeSource (object
) Type: object
Properties None
"},{"location":"arcaflow/running/setup/#logging","title":"Logging","text":"Logging is useful when you need more information about what is happening while you run a workload.
"},{"location":"arcaflow/running/setup/#basic-logging","title":"Basic logging","text":"Here is the syntax for setting the log level: config.yaml
log:\n level: info\n
Options for the level
are:
debug
: Extra verbosity useful to developers info
: General info warning
: Something went wrong, and you should know about it error
: Something failed. This inf o should help you figure out why
This sets which types of log output are shown or hidden. debug
shows everything, while error
shows the least, only showing error
output. Each output shows more, rather than just its type, so debug
, info
, and warning
still show error
output.
"},{"location":"arcaflow/running/setup/#step-logging","title":"Step logging","text":"Step logging is useful for getting output from failed steps, or general debugging. It is not recommended that you rely on this long term, as there may be better methods of debugging failed workflows.
To make the workflow output just error
level logs when a step fails, set it as shown: config.yaml
logged_outputs:\n error:\n level: error\n
Tip
The standard name for the output path when a step fails is called error
, which happens to also be the name of the log level here, but these are independent values.
You can specify multiple types of outputs and their log levels. For example, if you also want to output success steps as debug, set it as shown: config.yaml
logged_outputs:\n error:\n level: error\n success:\n level: debug\n
Note: If you set the level lower than the general log level shown above, it will not show up in the output.
"},{"location":"arcaflow/workflows/","title":"Creating Arcaflow workflows","text":"Arcaflow workflows consist of four parts:
Version The schema version must be at the root of your workflow file. It indicates the semantic version of the workflow file structure being used.
Learn more about versioning \u00bb
Inputs The input section of a workflow is much like a plugin schema: it describes the data model of the workflow itself. This is useful because the input can be validated ahead of time. Any input data can then be referenced by the individual plugin steps.
Learn more about inputs \u00bb
Steps Steps hold the individual parts of the workflow. You can feed data from one step to the next, or feed data from the input to a step.
Learn more about steps \u00bb
Outputs Outputs hold the final result of a workflow. Outputs can reference outputs of steps.
Learn more about output \u00bb
Schema Names Learn more about our schema naming conventions \u00bb
"},{"location":"arcaflow/workflows/expressions/","title":"Arcaflow expressions","text":"Arcaflow expressions were inspired by JSONPath but have diverged from the syntax. You can use expressions in a workflow YAML like this:
some_value: !expr $.your.expression.here\n
This page explains the language elements of expressions.
Warning
Expressions in workflow definitions must be prefixed with !expr
, otherwise their literal value will be taken as a string.
"},{"location":"arcaflow/workflows/expressions/#literals","title":"Literals","text":"Literals represent constant values in an expression.
"},{"location":"arcaflow/workflows/expressions/#string-values","title":"String values","text":"Normal string literals start and end with a matched pair of either single quotes ('
) or double quotes (\"
) and have zero or more characters between the quotes.
Strings may contain special characters. In normal strings, these characters are represented by \u201cescape sequences\u201d consisting of a backslash followed by another character. Since a backslash therefore has a special meaning, in order to represent a literal backslash character, it must be preceded by another backslash. Similarly, in a string delimited by double quotes, a double quote occurring inside the string must be escaped to prevent it from marking the end of the string. The same is true for single quotes occurring inside a string delimited by single quotes. However, you do not need to escape double quotes in a single-quoted string nor single-quotes in a double-quoted string.
Here is the list of supported escape characters:
Escape Result \\\\
\\
backslash character \\t
tab character \\n
newline character \\r
carriage return character \\b
backspace character \\\"
\"
double quote character \\'
'
single quote character \\0
null character For example, to have the following text represented in a single string:
test test2/\\
You would need the expression \"test\\ntest2/\\\\\"
"},{"location":"arcaflow/workflows/expressions/#string-expressions-in-yaml","title":"String Expressions in YAML","text":"When expressing string literals in YAML, be aware that YAML has its own rules around the use of quotation marks.
For example, to include a double-quoted string in an expression, you must either add single quotes around the expression or use block flow scalars. Inside a single-quoted string, an apostrophe needs to be preceded by another apostrophe to indicate that it does not terminate the string.
Here is an example of the following value represented in a few of the various ways:
Here\u2019s an apostrophe and \u201cembedded quotes\u201d.
Inlined with single quotes:
some_value_1: !expr '\"Here''s an apostrophe and \\\"embedded quotes\\\".\"'\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The single quotes cause the YAML processor to pass the contents of the string intact except for replacing the repeated apostrophe with a single one. (They are not included in the expression value.)
- The backslash-escapes are replaced by Arca\u2019s expression processing. (The unescaped double quotes are not included in the expression value.)
Inlined with double quotes:
some_value_2: !expr \"'Here\\\\'s an apostrophe and \\\"embedded quotes\\\".'\"\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The double quotes cause the YAML processor to interpret the contents of the string:
- the
\\\\
is replaced with a single backslash; - each
\\\"
is replaced with a literal \"
; - the surrounding double quotes are not included in the expression value.
- The backslash-escapes are replaced by Arca\u2019s expression processing. (The unescaped single quotes are not included in the expression value.)
With Block Flow Scalar:
some_value_1: !expr |-\n 'Here\\'s an apostrophe and \"embedded quotes\".'\nsome_value_2: !expr |-\n \"Here's an apostrophe and \\\"embedded quotes\\\".\"\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The vertical bar (
|
) causes the YAML processor to pass the contents of the string without modification. - Newlines within the expression are included in the string; the hyphen (
-
) after the vertical bar causes the trailing newline to be omitted from the end of the string. - The backslash-escapes are replaced by Arca\u2019s expression processing. The unescaped quotes are not included in the expression value; the other quotes are escaped to prevent them from ending the string prematurely; the double quotes in
some_value_1
do not need to be escaped nor do the single quotes in some_value_2
.
See Raw string values to see how to do this without escaping.
"},{"location":"arcaflow/workflows/expressions/#raw-string-values","title":"Raw string values","text":"Raw string literals start and end with backtick characters \u201c`\u201c.
In a raw string, all characters are interpreted literally. This means that you can use '
and \"
characters without escaping them, and backslashes are treated like any other character. However, backtick characters cannot appear in a raw string.
Here is an example of the following value represented using raw strings:
Here\u2019s an apostrophe and \u201cembedded quotes\u201d.
Inlined:
some_value: !expr '`Here''s an apostrophe and \"embedded quotes\".`'\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The single quotes cause the YAML processor to pass the contents of the string intact except for replacing the repeated apostrophe with a single one. (They are not included in the expression value.)
- The backticks cause Arca\u2019s expression processing to use the string verbatim. (The backticks are not included in the expression value.)
With Block Flow Scalar:
some_value: !expr |-\n `Here's an apostrophe and \"embedded quotes\".`\n
Tip
- The
!expr
tag indicates to the YAML processor that the value is an Arca expression. - The vertical bar (
|
) causes the YAML processor to pass the contents of the string without modification. - Newlines within the expression are included in the string; the hyphen (
-
) after the vertical bar causes the trailing newline to be omitted from the end of the string. - The backticks cause Arca\u2019s expression processing to use the string verbatim, thus the embedded quotes don\u2019t require escapes. (The backticks are not included in the expression value.)
"},{"location":"arcaflow/workflows/expressions/#integer-numbers","title":"Integer numbers","text":"Integers are whole numbers expressed as sequences of base-10 digits.
Integer literals may not start with 0
, unless the value is 0
. For example, 001
is not a valid integer literal.
Examples:
0
1
503
Negative values are constructed by applying the negation operator (-
) to a literal numeric value.
"},{"location":"arcaflow/workflows/expressions/#floating-point-numbers","title":"Floating point numbers","text":"Floating point literals are non-negative double-precision floating point numbers.
Supported formats include:
- number characters followed by a period followed by zero or more number characters:
1.1
or 1.
- base-10 exponential scientific notation formats like
5.0e5
and 5.0E-5
Negative values are constructed by applying the negation operator (-
) to a literal numeric value.
"},{"location":"arcaflow/workflows/expressions/#boolean-values","title":"Boolean values","text":"Boolean literals have two valid values:
true
false
No other values are valid boolean literals. The values are case-sensitive.
"},{"location":"arcaflow/workflows/expressions/#root-reference","title":"Root reference","text":"The $
character always references the root of the data structure. Let\u2019s take this data structure:
foo:\n bar: Hello world!\n
You can reference the text like this:
$.foo.bar\n
"},{"location":"arcaflow/workflows/expressions/#dot-notation","title":"Dot notation","text":"The dot notation allows you to reference fields of an object.
For example, if you have an object on the root data structure named \u201ca\u201d with the field \u201cb\u201d in it, you can access it with:
$.a.b\n
"},{"location":"arcaflow/workflows/expressions/#bracket-accessor","title":"Bracket accessor","text":"The bracket accessor is used for referencing values in maps or lists.
"},{"location":"arcaflow/workflows/expressions/#list-access","title":"List access","text":"For list access, you specify the index of the value you want to access. The index should be an expression yielding a non-negative integer value, where zero corresponds to the first value in the list.
If you have a list named foo
:
foo:\n - Hello world!\n
You can access the first value with the expression:
$.foo[0]\n
Giving the output \"Hello world!\"
"},{"location":"arcaflow/workflows/expressions/#map-access","title":"Map access","text":"Maps, also known as dictionaries in some languages, are key-value pair data structures.
To use a map in an expression, the expression to the left of the brackets must be a reference to a map. That is then followed by a pair of brackets with a sub-expression between them. That sub-expression must evaluate to a valid key in the map.
Here is an example of a map with string keys and integer values. The map is stored in a field called foo
in the root-level object:
foo:\n a: 1\n b: 2\n
Given the map shown above, the following expression would yield a value of 2
:
$.foo[\"b\"]\n
"},{"location":"arcaflow/workflows/expressions/#functions","title":"Functions","text":"The engine provides predefined functions for use in expressions. These provide transformations beyond what is available from operators.
Functions:
function definition return type description intToFloat(integer)
float Converts an integer value into the equivalent floating point value. floatToInt(float)
integer Converts a floating point value into an integer value by discarding the fraction, rounding toward zero to the nearest integer.Special cases:\u00a0 +Inf yields the maximum 64-bit integer (9223372036854775807)\u00a0 -Inf and NaN yield the minimum 64-bit integer (-9223372036854775808)For example, 5.5
yields 5
, and -1.9
yields -1
intToString(integer)
string Returns a string containing the base-10 representation of the input.For example, an input of 55
yields \"55\"
floatToString(float)
string Returns a string containing the base-10 representation of the input.For example, an input of 5000.5
yields \"5000.5\"
floatToFormattedString(float, string, integer)
string Returns a string containing the input in the specified format with the specified precision.\u00a0 Param 1: the floating point input value\u00a0 Param 2: the format specifier: \"e\"
, \"E\"
, \"f\"
, \"g\"
, \"G\"
\u00a0 Param 3: the number of digitsSpecifying -1 for the precision will produce the minimum number of digits required to represent the value exactly. (See the Go runtime documentation for details.) boolToString(boolean)
string Returns \"true\"
for true
, and \"false\"
for false
. stringToInt(string)
integer Interprets the string as a base-10 integer. Returns an error if the input is not a valid integer. stringToFloat(string)
float Converts the input string to a double-precision floating-point number.Accepts floating-point numbers as defined by the Go syntax for floating point literals. If the input is well-formed and near a valid floating-point number, returns the nearest floating-point number rounded using IEEE754 unbiased rounding. Returns an error when an invalid input is received. stringToBool(string)
boolean Interprets the input as a boolean.Accepts \"1\"
, \"t\"
, and \"true\"
as true
and \"0\"
, \"f\"
, and \"false\"
as false
(case is not significant). Returns an error for any other input. ceil(float)
float Returns the least integer value greater than or equal to the input.Special cases are:\u00a0 ceil(\u00b10.0) = \u00b10.0\u00a0 ceil(\u00b1Inf) = \u00b1Inf\u00a0 ceil(NaN) = NaNFor example ceil(1.5)
yields 2.0
, and ceil(-1.5)
yields -1.0
floor(float)
float Returns the greatest integer value less than or equal to the input.Special cases are:\u00a0 floor(\u00b10.0) = \u00b10.0\u00a0 floor(\u00b1Inf) = \u00b1Inf\u00a0 floor(NaN) = NaNFor example floor(1.5)
yields 1.0
, and floor(-1.5)
yields -2.0
round(float)
float Returns the nearest integer to the input, rounding half away from zero.Special cases are:\u00a0 round(\u00b10.0) = \u00b10.0\u00a0 round(\u00b1Inf) = \u00b1Inf\u00a0 round(NaN) = NaNFor example round(1.5)
yields 2.0
, and round(-1.5)
yields -2.0
abs(float)
float Returns the absolute value of the input.Special cases are:\u00a0 abs(\u00b1Inf) = +Inf\u00a0 abs(NaN) = NaN toLower(string)
string Returns the input with Unicode letters mapped to their lower case. toUpper(string)
string Returns the input with Unicode letters mapped to their upper case. splitString(string, string)
list[string] Returns a list of the substrings which appear between instances of the specified separator; the separator instances are not included in the resulting list elements; adjacent occurrences of separator instances as well as instances appearing at the beginning or ending of the input will produce empty string list elements.\u00a0 Param 1: The string to split.\u00a0 Param 2: The separator. readFile(string)
string Returns the contents of a file as a UTF-8 character string, given a file path string. Relative file paths are resolved from the Arcaflow process working directory. Shell environment variables are not expanded. bindConstants(list[any], any)
list[object] Returns a list of objects each containing two properties: an item
property which contains the corresponding item from the list in the first parameter; and, a constant
property which contains the value of the second input parameter. The output list items will have a generated schema name as described in Generated Schema Names. For usage see this example. A function is used in an expression by referencing its name followed by a comma-separated list of zero or more argument expressions enclosed in parentheses.
Example:
thisIsAFunction(\"this is a string literal for the first parameter\", $.a.b)\n
"},{"location":"arcaflow/workflows/expressions/#binary-operations","title":"Binary Operations","text":"Binary Operations have an expression to the left and right, with an operator in between. The order of operations determines which operators are evaluated first. See Order of Operations
The types of the left and right operand expressions must match. To convert between types, see the list of available functions. The type of the resulting expression is the same as the type of its operands.
Operator Description +
Addition/Concatenation -
Subtraction *
Multiplication /
Division %
Modulus ^
Exponentiation ==
Equal To !=
Not Equal To >
Greater Than <
Less Than >=
Greater Than or Equal To <=
Less Than or Equal To &&
Logical And \\|\\|
Logical Or"},{"location":"arcaflow/workflows/expressions/#additionconcatenation","title":"Addition/Concatenation","text":"This operator has different behavior depending on the type.
"},{"location":"arcaflow/workflows/expressions/#string-concatenation","title":"String Concatenation","text":"When the +
operator is used with two strings, it concatenates them together. For example, the expression \"a\" + \"b\"
would output the string \"ab\"
.
"},{"location":"arcaflow/workflows/expressions/#mathematical-addition","title":"Mathematical Addition","text":"When the +
operator is used with numerical operands, it adds them together. The operator requires numerical operands with the same type. You cannot mix float and integer operands. For example, the expression 2 + 2
would output the integer 4
.
"},{"location":"arcaflow/workflows/expressions/#subtraction","title":"Subtraction","text":"When the -
operator is applied to numerical operands, the result is the value of the right operand subtracted from the value of the left. The operator requires numerical operands with the same type. You cannot mix float and integer operands.
For example, the expression 6 - 4
would output the integer 2
. The expression $.a - $.b
would evaluate the values of a
and b
within the root, and subtract the value of $.b
from $.a
.
"},{"location":"arcaflow/workflows/expressions/#multiplication","title":"Multiplication","text":"When the *
operator is used with numerical operands, it multiplies them. The operator requires numerical operands with the same type.
For example, the expression 3 * 3
would output the integer 9
.
"},{"location":"arcaflow/workflows/expressions/#division","title":"Division","text":"When the /
operator is used with numerical operands, it outputs the value of the left expression divided by the value of the right. The operator requires numerical operands with the same type.
The result of integer division is rounded towards zero. If a non-integral result is required, or if different rounding logic is required, convert the inputs into floating point numbers with the intToFloat
function. Different types of rounding can be performed on floating point numbers with the functions ceil
, floor
, and round
.
For example, the expression -3 / 2
would yield the integer value -1
.
"},{"location":"arcaflow/workflows/expressions/#modulus","title":"Modulus","text":"When the %
operator is used with numerical operands, it evaluates to the remainder when the value of the left expression is divided by the value of the right. The operator requires numerical operands with the same type.
For example, the expression 5 % 3
would output the integer 2
.
"},{"location":"arcaflow/workflows/expressions/#exponentiation","title":"Exponentiation","text":"The ^
operator outputs the result of the left side raised to the power of the right side. The operator requires numerical operands with the same type.
The mathematical expression 23 is represented in the expression language as 2^3
, which would output the integer 8
.
"},{"location":"arcaflow/workflows/expressions/#equal-to","title":"Equal To","text":"The ==
operator evaluates to true if the values of the left and right operands are the same. Both operands must have the same type. You may use functions to convert between types \u2013 see functions for more type conversions. The operator supports the types integer
, float
, string
, and boolean
.
For example, 2 == 2
results in true
, and \"a\" == \"b\"
results in false
. 1 == 1.0
would result in a type error.
"},{"location":"arcaflow/workflows/expressions/#not-equal-to","title":"Not Equal To","text":"The !=
operator is the inverse of the ==
operator. It evaluates to false if the values of the left and right operands are the same. Both operands must have the same type. You may use functions to convert between types \u2013 see functions for more type conversions. The operator supports the types integer
, float
, string
, and boolean
.
For example, 2 != 2
results in false
, and \"a\" != \"b\"
results in true
. 1 != 1.0
would result in a type error.
"},{"location":"arcaflow/workflows/expressions/#greater-than","title":"Greater Than","text":"The >
operator outputs true
if the left side is greater than the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For an integer example, the expression 3 > 3
would output the boolean false
, and 4 > 3
would output true
. For a string example, the expression \"a\" > \"b\"
would output false
.
"},{"location":"arcaflow/workflows/expressions/#less-than","title":"Less Than","text":"The <
operator outputs true
if the left side is less than the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For an integer example, the expression 3 < 3
would output the boolean false
, and 1 < 2
would output true
. For a string example, the expression \"a\" < \"b\"
would output true
.
"},{"location":"arcaflow/workflows/expressions/#greater-than-or-equal-to","title":"Greater Than or Equal To","text":"The >=
operator outputs true
if the left side is greater than or equal to (not less than) the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For an integer example, the expression 3 >= 3
would output the boolean true
, 3 >= 4
would output false
, and 4 >= 3
would output true
.
"},{"location":"arcaflow/workflows/expressions/#less-than-or-equal-to","title":"Less Than or Equal To","text":"The <=
operator outputs true
if the left side is less than or equal to (not greater than) the right side, and false
otherwise. The operator requires numerical or string operands. The type must be the same for both operands. String operands are compared using the lexicographical order of the charset.
For example, the expression 3 <= 3
would output the boolean true
, 3 <= 4
would output true
, and 4 <= 3
would output false
.
"},{"location":"arcaflow/workflows/expressions/#logical-and","title":"Logical AND","text":"The &&
operator returns true
if both the left and right sides are true
, and false
otherwise. This operator requires boolean operands. Note: The operation does not \u201cshort-circuit\u201d \u2013 both the left and right expressions are evaluated before the comparison takes place.
All cases:
Left Right &&
true
true
true
true
false
false
false
true
false
false
false
false
"},{"location":"arcaflow/workflows/expressions/#logical-or","title":"Logical OR","text":"The ||
operator returns true
if either or both of the left and right sides are true
, and false
otherwise. This operator requires boolean operands. Note: The operation does not \u201cshort-circuit\u201d \u2013 both the left and right expressions are evaluated before the comparison takes place.
All cases:
Left Right \\|\\|
true
true
true
true
false
true
false
true
true
false
false
false
"},{"location":"arcaflow/workflows/expressions/#unary-operations","title":"Unary Operations","text":"Unary operations are operations that have one input. The operator is applied to the expression which follows it.
Operator Description - Negation ! Logical complement"},{"location":"arcaflow/workflows/expressions/#negation","title":"Negation","text":"The -
operator negates the value of the expression which follows it.
This operation requires numeric input.
Examples with integer literals: -5
, - 5
Example with a float literal: -50.0
Example with a reference: -$.foo
Example with parentheses and a sub-expression: -(5 + 5)
"},{"location":"arcaflow/workflows/expressions/#logical-complement","title":"Logical complement","text":"The !
operator logically inverts the value of the expression which follows it.
This operation requires boolean input.
Example with a boolean literal: !true
Example with a reference: !$.foo
"},{"location":"arcaflow/workflows/expressions/#parentheses","title":"Parentheses","text":"Parentheses are used to force precedence in the expression. They do not do anything implicitly (for example, there is no implied multiplication).
For example, the expression 5 + 5 * 5
evaluates the 5 * 5
before the +
, resulting in 5 + 25
, and finally 30
. If you want the 5 + 5 to be run first, you must use parentheses. That gives you the expression (5 + 5) * 5
, resulting in 10 * 5
, and finally 50
.
"},{"location":"arcaflow/workflows/expressions/#order-of-operations","title":"Order of Operations","text":"The order of operations is designed to match mathematics and most programming languages.
Order (highest to lowest; operators listed on the same line are evaluated in the order they appear in the expression):
- negation (
-
) - parentheses (
()
) - exponent (
^
) - multiplication (
*
) and division (/
) - addition/concatenation (
+
) and subtraction (-
) -
binary equality and inequality (all equal)
- equals (
==
) - not equals (
!=
) - greater than (
>
) - less than (
<
) - greater than or equal to (
>=
) - less than or equal to (
<=
)
-
logical complement (!
)
- logical AND (
&&
) - logical OR (
||
) - dot notation (
.
) and bracket access ([]
) "},{"location":"arcaflow/workflows/expressions/#other-information","title":"Other information","text":"More information on the expression language is available in the development guide.
"},{"location":"arcaflow/workflows/expressions/#examples","title":"Examples","text":""},{"location":"arcaflow/workflows/expressions/#referencing-inputs","title":"Referencing inputs","text":"Pass a workflow input directly to a plugin input
workflow.yaml
version: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n name:\n type:\n type_id: string\n\nsteps:\n step_a:\n plugin:\n deployment_type: image\n src: quay.io/some/container/image\n input:\n some:\n key: !expr $.input.name\n
"},{"location":"arcaflow/workflows/expressions/#passing-between-steps","title":"Passing between steps","text":"Pass output from one plugin to the input of another plugin
workflow.yamlversion: v0.2.0\nsteps:\n step_a:\n plugin: \n deployment_type: image\n src: quay.io/some/container/image\n input: {}\n\n step_b:\n plugin:\n deployment_type: image \n src: quay.io/some/container/image\n input:\n some:\n key: !expr $.steps.step_a.outputs.success.some_value\n
"},{"location":"arcaflow/workflows/expressions/#binding-constants","title":"Binding Constants","text":"input.yamlrepeated_inputs: \n hostname: mogo\nvarying_inputs:\n - cpu_load: 10\n - cpu_load: 20\n - cpu_load: 40\n - cpu_load: 60\n
workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n repeated_inputs:\n type:\n type_id: ref\n id: RepeatedValues\n varying_inputs:\n type:\n type_id: list\n items:\n id: SubRootObject\n type_id: ref\n RepeatedValues:\n id: RepeatedValues\n properties:\n hostname:\n type:\n type_id: string\n SubRootObject:\n id: SubRootObject\n properties:\n cpu_load:\n type:\n type_id: integer \n\nsteps:\n example:\n plugin:\n deployment_type: image \n src: quay.io/some/container/image\n input: !expr 'bindConstants($.input.varying_inputs, $.input.repeated_inputs)'\n
In this case, we do not need to know the schema name of the type output generated by bindConstants()
. If you need to reference the schema of the list items returned by bindConstants()
, see Generated Schema Name.
"},{"location":"arcaflow/workflows/flow-control/","title":"Using Flow Control Mechanics","text":"Flow control allows the workflow author to build a workflow with a decision tree based on supported flow logic. These flow control operations are not implemented by plugins, but are part of the workflow engine itself.
"},{"location":"arcaflow/workflows/flow-control/#conditional-step-execution","title":"Conditional Step Execution","text":"Conditional step execution can be achieved with the enabled
input.
The value passed into the enabled
field should be an expression that resolves as a boolean. So it should either reference a path to a boolean value, or it should be a binary comparison.
A situation that would benefit from conditional step execution includes a step that uploads the result to a remote server. For development runs you may not want to upload the results, so you can create a bool
field in the input to toggle the upload, then you can reference that field in the enabled
field of the upload step.
Example: workflow.yaml
version: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n step_1_enabled:\n type:\n type_id: bool\n step_1_input:\n type:\n type_id: integer\nsteps:\n my_step:\n plugin:\n deployment_type: image\n src: path/to/my_plugin:1\n input:\n param_1: !expr $.input.step_1_input\n enabled: !expr $.input.step_1_enabled\noutputs:\n workflow_success:\n plugin_output: !expr $.steps.my_step.outputs.success\n
But oh no! The workflow fails with the error all outputs marked as unresolvable
when the step is disabled. This happens because the workflow instructed the step not to run, and therefore the output workflow_success
cannot get the information it needs. See the next section for methods of handling this situation.
"},{"location":"arcaflow/workflows/flow-control/#graceful-handling-of-disabled-steps","title":"Graceful handling of disabled steps","text":"When steps are disabled, they no longer emit their step output. To handle this, the workflow output (or a following step) must have an OR dependency on both the step\u2019s disable output and on another step that has opposite enable logic. An OR dependency means that the step will resolve when any of the nodes it depends on resolve, unlike the default AND logic that waits for all dependencies to resolve.
Here is a visual demonstrating two steps that have opposite logic:
stateDiagram-v2\n classDef tag font-style:italic;\n classDef step font-weight:bold;\n\n state if_state <<choice>>\n Step1:::step: Step 1\n [*] --> Step1\n Step1 --> if_state\n Step2:::step: Step 2\n Step3:::step: Step 3\n if_state --> Step2: !expr $.step1.output_1 == true\n if_state --> Step3: !expr $.step1.output_1 == false\n oneof:::tag: !oneof\n Step2 --> oneof\n Step3 --> oneof\n oneof --> [*]
Here is a visual demonstrating utilizing the disabled output:
stateDiagram-v2\n classDef tag font-style:italic;\n classDef step font-weight:bold;\n\n input\n [*] --> Step1:::step\n input --> Step1: !expr $.input.enabled\n or_disabled:::tag: !ordisabled\n Step1 --> or_disabled: success\n Step1 --> or_disabled: Disabled\n or_disabled --> [*]
The way this works is that either of the two paths can create a valid output, allowing the workflow to continue past the disabled steps.
"},{"location":"arcaflow/workflows/flow-control/#how-to-do-this-in-a-workflow","title":"How to do this in a workflow","text":"There are two tags that create the described OR dependency.
Tag Description !oneof
A general use tag to depend on any of the inputs !ordisabled
A special case of !oneof
that automatically handles the disabled case"},{"location":"arcaflow/workflows/flow-control/#how-to-use-oneof","title":"How to use !oneof
","text":"To use !oneof
for graceful handling of disabled steps, the oneof should depend on ONE OF the success output and the disabled output, which will output either the success output or the disabled output.
The oneof tag is a method of creating a schema one_of_string
type from values present in the workflow.
The syntax of !oneof
is: - Following the tag !oneof
, create a new YAML section (map) by starting an indented new line. That section should contain two properties: - discriminator: A string that specifies what the oneof discriminator should be. The discriminator specifies which option was emitted. - options: A YAML section (map) that contains all options. The keys are the discriminator values, and the values should be valid expressions.
Example: workflow.yaml
version: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n step_1_enabled:\n type:\n type_id: bool\n step_1_input:\n type:\n type_id: integer\nsteps:\n my_step:\n plugin:\n deployment_type: image\n src: path/to/my_plugin:1\n input:\n param_1: !expr $.input.step_1_input\n enabled: !expr $.input.step_1_enabled\noutputs:\n workflow_success:\n plugin_output: !oneof\n discriminator: result\n options:\n enabled: !expr $.steps.my_step.outputs.success\n disabled: !expr $.steps.my_step.disabled.output\n
"},{"location":"arcaflow/workflows/flow-control/#how-to-use-ordisabled","title":"How to use !ordisabled
","text":"Arcaflow provides workflow developers an easy way to handle disabled steps that is as simple as changing the expression tag from !expr
to !ordisabled
.
This is a special case of !oneof
with the discriminator set to result
, and the options set to enabled
and disabled
. The enabled output is the expression provided, and the disabled output is automatically generated from the expression provided.
The input must be in one of the following formats: - $.steps.step_name.outputs
- steps.step_name.outputs
- $.steps.step_name.outputs.output_name
- steps.step_name.outputs.output_name
- $.steps.step_name.outputs.output_name.output_field
- steps.step_name.outputs.output_name.output_field
Using !ordisabled
, the following example is otherwise equivalent to the prior example: workflow.yaml
version: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n step_1_enabled:\n type:\n type_id: bool\n step_1_input:\n type:\n type_id: integer\nsteps:\n my_step:\n plugin:\n deployment_type: image\n src: path/to/my_plugin:1\n input:\n param_1: !expr $.input.step_1_input\n enabled: !expr $.input.step_1_enabled\noutputs:\n workflow_success:\n plugin_output: !ordisabled $.steps.my_step.outputs.success\n
"},{"location":"arcaflow/workflows/flow-control/#alternative-methods","title":"Alternative methods","text":"For handling disabled steps, !oneof
and !ordisabled
are the recommended methods because they cause output failure when the step fails. However, if it is acceptable for the workflow to succeed when a step crashes, the optional tags could be the right feature for your workflow.
The alterative methods are the \u201coptional\u201d family of expression tags. The oneof
tags instructed the workflow to build a oneof_string
type with OR dependencies, requiring one of the options to have an output for the oneof to resolve. Meanwhile, the \u201coptional\u201d family of tags can resolve without an output by utilizing optional object property fields.
Tag Description !wait-optional
The typical one that should be used. It will resolve either when the value becomes present, or when it is known that the value will not resolve. !soft-optional
Not recommended for most use cases. It will not wait for the value to be ready. If the value is present, it will set the field. If it isn\u2019t, it will exclude it."},{"location":"arcaflow/workflows/flow-control/#how-to-use-wait-optional","title":"How to use !wait-optional
","text":"workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n step_1_enabled:\n type:\n type_id: bool\n step_1_input:\n type:\n type_id: integer\nsteps:\n my_step:\n plugin:\n deployment_type: image\n src: path/to/my_plugin:1\n input:\n param_1: !expr $.input.step_1_input\n enabled: !expr $.input.step_1_enabled\noutputs:\n workflow_success:\n plugin_output: !wait-optional $.steps.my_step.outputs.success\n
If my_step
is enabled and has an output, it will be present in the plugin_output
field of the workflow_success
output. Otherwise, the workflow_success
output will not include the plugin_output
field. The output will wait for the plugin to finish, if enabled.
"},{"location":"arcaflow/workflows/flow-control/#how-to-use-soft-optional","title":"How to use !soft-optional
","text":"Warning
This feature is not recommended for most use cases.
The !soft-optional
tag creates the loosest type of dependency. The output will not wait for the step to finish or fail. In the event that the step referenced with the !soft-optional
tag does not finish by the time the other dependencies resolve, or if it crashes or is disabled, the field will be left out of the output.
The example used in !wait-optional
would not work with !soft-optional
because the output would immediately resolve without the plugin_output
field. A second dependency is required to ensure the output does not resolve immediately.
workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n background_step_enabled:\n type:\n type_id: bool\n background_step_input:\n type:\n type_id: integer\n step_1_input:\n type:\n type_id: integer\nsteps:\n background_step:\n plugin:\n deployment_type: image\n src: path/to/my_background_plugin:1\n input:\n param_1: !expr $.input.background_step_input\n enabled: !expr $.input.background_step_enabled\n my_step:\n plugin:\n deployment_type: image\n src: path/to/my_plugin:1\n input:\n param_1: !expr $.input.step_1_input\noutputs:\n workflow_success:\n background_output: !soft-optional $.steps.background_step.outputs.success\n plugin_output: $.steps.my_step.outputs.success\n
"},{"location":"arcaflow/workflows/flow-control/#foreach-loops","title":"Foreach Loops","text":"Foreach loops allow for running a sub-workflow with iterative inputs from a parent workflow. A sub-workflow is a complete Arcaflow workflow file with its own input and output schemas as described in this section. The inputs for the sub-workflow are provided as a list, where each list item is an object that matches the sub-workflow input schema.
Tip
A complete functional example is available in the arcaflow-workflows repository.
In the parent workflow file, the author can define an input schema with the list that will contain the input object that will be passed to the sub-workflow. For example:
workflow.yamlinput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n loop:\n type:\n type_id: list\n items:\n type_id: object\n id: loop_id\n properties:\n loop_id:\n type:\n type_id: integer\n param_1:\n type:\n type_id: integer\n param_2:\n type:\n type_id: string\n
Then in the steps
section of the workflow, the sub-workflow can be defined as a step with the loop
list object from above passed to its input.
The parameters for the sub-workflow step are:
kind
- The type of loop (currently only foreach is supported) items
- A list of objects to pass to the sub-workflow (the expression language allows to pass this from the input schema per the above example) workflow
- The file name for the sub-workflow (this should be in the same directory as the parent workflow) parallelism
- The number of sub-workflow loop iterations that will run in parallel
workflow.yamlsteps:\n sub_workflow_loop:\n kind: foreach\n items: !expr $.input.loop\n workflow: sub-workflow.yaml\n parallelism: 1\n
The input yaml file for the above parent workflow would provide the list of objects to loop over as in this example:
input.yamlloop:\n - loop_id: 1\n param_1: 10\n param_2: \"a\"\n - loop_id: 2\n param_1: 20\n param_2: \"b\"\n - loop_id: 3\n param_1: 30\n param_2: \"c\"\n
The sub-workflow file then has its complete schema and set of steps as in this example:
sub-workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n loop_id:\n type:\n type_id: integer\n param_1:\n type:\n type_id: integer\n param_2:\n type:\n type_id: string\nsteps:\n my_plugin:\n plugin:\n deployment_type: image\n src: path/to/my_plugin:1\n input:\n param_1: !expr $.input.param_1\n my_other_plugin:\n plugin:\n deployment_type: image\n src: path/to/my_other_plugin:1\n input:\n param_2: !expr $.input.param_2\noutputs:\n success:\n loop_id: !expr $.input.loop_id\n my_plugin: !expr $.steps.my_plugin.outputs.success\n my_other_plugin: !expr $.steps.my_other_plugin.outputs.success\n
"},{"location":"arcaflow/workflows/flow-control/#reduce-repetition-with-bindconstants","title":"Reduce Repetition with bindConstants()
","text":"The builtin function bindConstants()
allows you to avoid repeating input variables for a foreach
subworkflow. In the example below, the input variable name
\u2019s value is repeated across each iteration in this input. This results in a more repetitive input and schema definition. This section will show you how to simplify it.
"},{"location":"arcaflow/workflows/flow-control/#workflow-and-input-before-bindconstants","title":"Workflow and Input Before bindConstants()
","text":"input-repeated.yamliterations:\n - loop_id: 1\n repeated_inputs:\n name: mogo\n ratio: 3.14\n - loop_id: 2\n repeated_inputs:\n name: mogo\n ratio: 3.14\n - loop_id: 3\n repeated_inputs:\n name: mogo\n ratio: 3.14\n - loop_id: 4\n repeated_inputs:\n name: mogo\n ratio: 3.14\n
workflow.yamlversion: v0.2.0\ninput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n iterations:\n type:\n type_id: list\n items:\n id: SubRootObject\n type_id: ref\n namespace: $.steps.foreach_loop.execute.inputs.items\n\nsteps:\n foreach_loop:\n kind: foreach\n items: !expr $.input.iterations\n workflow: subworkflow.yaml\n parallelism: 1\n\noutputs:\n success:\n fab_four: !expr $.steps.foreach_loop.outputs.success.data\n
subworkflow.yamlversion: v0.2.0\ninput:\n root: SubRootObject\n objects:\n SubRootObject:\n id: SubRootObject\n properties:\n loop_id:\n type:\n type_id: integer\n repeated_inputs:\n type:\n type_id: ref\n id: RepeatedInputs\n RepeatedInputs:\n id: RepeatedInputs\n properties:\n name:\n type:\n type_id: string\n ratio:\n type:\n type_id: float\n\nsteps:\n example:\n plugin:\n deployment_type: image\n src: quay.io/arcalot/arcaflow-plugin-template-python:0.4.0\n input:\n name: !expr $.input.repeated_inputs.name\n\noutputs:\n success:\n loop_id: !expr $.input.loop_id\n ratio: !expr $.input.repeated_inputs.ratio\n beatle: !expr $.steps.example.outputs.success\n
"},{"location":"arcaflow/workflows/flow-control/#reduced-repetition-workflow","title":"Reduced Repetition Workflow","text":"Here we restructure the input, factoring out the repeated name
and ratio
entries in the list and placing them into a single field; we will use bindConstants()
to construct the foreach
list with repeated entries.
input.yamlrepeated_inputs: \n name: mogo\n ratio: 3.14\niterations:\n - loop_id: 1\n - loop_id: 2\n - loop_id: 3\n - loop_id: 4\n
To use the generated values from bindConstants()
, a new schema representing these bound values must be added to the input schema section of our subworkflow.yaml
, input
. This new schema\u2019s ID will be the ID of the schema that defines the items in your list, in this case SubRootObject
and the schema name that defines your repeated inputs, in this case RepeatedValues
, for more information see Generated Schema Names. This creates our new schema ID, SubRootObject__RepeatedValues
. You are required to use this schema ID because it is generated from the names of your other schemas.
workflow.yamlsteps:\n foreach_loop:\n kind: foreach\n items: !expr 'bindConstants($.input.iterations, $.input.repeated_inputs)'\n workflow: subworkflow.yaml\n parallelism: 1\n
To use bindConstants()
with an outputSchema
in your workflow, you need to reference the schema of the list items returned by bindConstants()
, see Generated Schema Name.
See the full workflow.
"},{"location":"arcaflow/workflows/input/","title":"Writing workflow inputs","text":"The input section of a workflow is much like a plugin schema: it describes the data model of the workflow itself. This is useful because the input can be validated ahead of time. Any input data can then be referenced by the individual steps.
Tip
The workflow input schema is analogous to the plugin input schema in that it defines the expected inputs and formats. But a workflow author has the freedom to define the schema independently of the plugin schema \u2013 This means that objects can be named and documented differently, catering to the workflow user, and input validation can happen before a plugin is loaded.
The workflow inputs start with a scope object. As an overview, a scope looks like this:
workflow.yamlinput:\n root: RootObject\n objects:\n RootObject:\n id: RootObject\n properties:\n name:\n type:\n type_id: string\n # Other properties of the root object\n # Other objects that can be referenced here\n
This corresponds to the following workflow input:
workflow_input.yamlname: Arca Lot\n
Admittedly, this looks complicated, but read on, it will become clear very quickly.
"},{"location":"arcaflow/workflows/input/#objects","title":"Objects","text":"Let\u2019s start with objects. Objects are like structs or classes in programming. They have two properties: an ID and a list of properties. The basic structure looks like this:
some_object:\n id: some_object\n properties:\n # Properties here\n
"},{"location":"arcaflow/workflows/input/#properties","title":"Properties","text":"Now you need to define a property. Let\u2019s say, we want to define a string with the name of the user. You can do this as follows:
type_id: object\nid: some_object\nproperties:\nname:\n type:\n type_id: string\n
Notice, that the type_id
field is indented. That\u2019s because the type
field describes a string type, which has additional parameters. For example:
type_id: object\nid: some_object\nproperties:\nname:\n type:\n type_id: string\n min: 1 # Minimum length for the string\n
There are also additional attributes of the property itself. For example:
type_id: object\nid: some_object\nproperties:\nname:\n type:\n type_id: string\n min: 1 # Minimum length for the string\n display:\n name: Name\n description: Name of the user.\n conflicts:\n - full_name\n
Properties have the following attributes:
Attribute Type Description display
Display
Display metadata of the property. See Display values. required
bool
If set to true, the field must always be filled. required_if
[]string
List of other properties that, if filled, lead to the current property being required. required_if_not
[]string
List of other properties that, if not filled, lead to the current property being required. conflicts
[]string
List of other properties that conflict the current property. default
string
Default value for this property, JSON-encoded. examples
[]string
Examples for the current property, JSON-encoded. Note
Unlike the plugin schema where an unassigned default value is set to None
, for the workflow schema you simply omit the default to leave it unassigned.
"},{"location":"arcaflow/workflows/input/#scopes-and-refs","title":"Scopes and refs","text":"Scopes behave like objects, but they serve an additional purpose. Suppose, object A
had a property of the object type B
, but now you needed to reference back to object A
. Without references, there would be no way to do this.
OpenAPI and JSON Schema have a similar concept, but in those systems all references are global. This presents a problem when merging schemas. For example, both Docker and Kubernetes have an object called Volume
. These objects would need to be renamed when both configurations are in one schema.
Arcaflow has a different solution: every plugin, every part of a workflow has its own scope. When a reference is found in a scope, it always relates to its own scope. This way, references don\u2019t get mixed.
Let\u2019s take a simple example: a scope with objects A
and B
, referencing each other.
type_id: scope\nroot: A\nobjects:\n A:\n type_id: object\n id: A\n properties:\n b:\n type:\n type_id: ref\n id: B\n required: false\n B:\n type_id: object\n id: B\n properties:\n a:\n type:\n type_id: ref\n id: A\n required: false\n
This you can create a circular dependency between these objects without needing to copy-paste their properties.
Additionally, refs have an extra display
property, which references a Display value to provide context for the reference.
"},{"location":"arcaflow/workflows/input/#strings","title":"Strings","text":"Strings are, as the name suggests, strings of human-readable characters. They have the following properties:
type_id: string\nmin: # Minimum number of characters. Optional.\nmax: # Maximum number of characters. Optional.\npattern: # Regular expression this string must match. Optional.\n
"},{"location":"arcaflow/workflows/input/#pattern","title":"Pattern","text":"Patterns are special kinds of strings that hold regular expressions.
type_id: pattern\n
"},{"location":"arcaflow/workflows/input/#integers","title":"Integers","text":"Integers are similar to strings, but they don\u2019t have a pattern
field but have a units
field. (See Units.)
type_id: integer\nmin: # Minimum value. Optional.\nmax: # Maximum value. Optional.\nunits:\n # Units definition. Optional.\n
"},{"location":"arcaflow/workflows/input/#floats","title":"Floats","text":"Floating point numbers are similar to integers.
type_id: float\nmin: # Minimum value. Optional.\nmax: # Maximum value. Optional.\nunits:\n # Units definition. Optional.\n
"},{"location":"arcaflow/workflows/input/#string-enums","title":"String enums","text":"Enums only allow a fixed set of values. String enums map string keys to a display value. (See Display values.)
type_id: enum_string\nvalues:\n red:\n name: Red\n yellow:\n name: Yellow\n
"},{"location":"arcaflow/workflows/input/#integer-enums","title":"Integer enums","text":"Enums only allow a fixed set of values. Integer enums map integer keys to a display value. (See Display values.)
type_id: enum_integer\nvalues:\n 1:\n name: Red\n 2:\n name: Yellow\n
"},{"location":"arcaflow/workflows/input/#booleans","title":"Booleans","text":"Booleans can hold a true or false value.
type_id: bool\n
"},{"location":"arcaflow/workflows/input/#lists","title":"Lists","text":"Lists hold items of a specific type. You can also define their minimum and maximum size.
type_id: list\nitems:\n type_id: type of the items\n # Other definitions for list items\nmin: 1 # Minimum number of items in the list (optional)\nmax: 2 # maximum number of items in the list (optional)\n
"},{"location":"arcaflow/workflows/input/#maps","title":"Maps","text":"Maps are key-value mappings. You must define both the key and value types, whereas keys can only be strings, integers, string enums, or integer enums.
type_id: map\nkeys:\n type_id: string\nvalues:\n type_id: string\nmin: 1 # Minimum number of items in the map (optional)\nmax: 2 # maximum number of items in the map (optional)\n
"},{"location":"arcaflow/workflows/input/#one-of-string-discriminator","title":"One-of (string discriminator)","text":"One-of types allow you to specify multiple alternative objects, scopes, or refs. However, these objects must contain a common field (discriminator) and each value for that field must correspond to exactly one object type.
Tip
If the common field is not specified in the possible objects, it is implicitly added. If it is specified, however, it must match the discriminator type.
type_id: one_of_string\ndiscriminator_field_name: object_type # Defaults to: _type\ntypes:\n a:\n type_id: object\n id: A\n properties:\n # Properties of object A.\n b:\n type_id: object\n id: B\n properties:\n # Properties of object B\n
We can now use the following value as an input:
object_type: a\n# Other values for object A\n
In contrast, you can specify object_type
as b
and that will cause the unserialization to run with the properties of object B
.
"},{"location":"arcaflow/workflows/input/#one-of-integer-discriminator","title":"One-of (integer discriminator)","text":"One-of types allow you to specify multiple alternative objects, scopes, or refs. However, these objects must contain a common field (discriminator) and each value for that field must correspond to exactly one object type.
Tip
If the common field is not specified in the possible objects, it is implicitly added. If it is specified, however, it must match the discriminator type.
type_id: one_of_int\ndiscriminator_field_name: object_type # Defaults to: _type\ntypes:\n 1:\n type_id: object\n id: A\n properties:\n # Properties of object A.\n 2:\n type_id: object\n id: B\n properties:\n # Properties of object B\n
We can now use the following value as an input:
object_type: 1\n# Other values for object A\n
In contrast, you can specify object_type
as 2
and that will cause the unserialization to run with the properties of object B
.
"},{"location":"arcaflow/workflows/input/#any-types","title":"Any types","text":"Any types allow any data to pass through without validation. We do not recommend using the \u201cany\u201d type due to its lack of validation and the risk to cause runtime errors. Only use any types if you can truly handle any data that is passed.
type_id: any\n
"},{"location":"arcaflow/workflows/input/#display-values","title":"Display values","text":"Display values are all across the Arcaflow schema. They are useful to provide human-readable descriptions of properties, refs, etc. that can be used to generate nice, human-readable documentation, user interfaces, etc. They are always optional and consist of the following 3 fields:
name: Short name\ndescription: Longer description of what the item does, possibly in multiple lines.\nicon: |\n <svg ...></svg> # SVG icon, 64x64 pixels, without doctype and external references.\n
"},{"location":"arcaflow/workflows/input/#units","title":"Units","text":"Units make it easier to parse and display numeric values. For example, if you have an integer representing nanoseconds, you may want to parse strings like 5m30s
.
Units have two parameters: a base unit description and multipliers. For example:
base_unit:\n name_short_singular: B\n name_short_plural: B\n name_long_singular: byte\n name_long_plural: bytes\nmultipliers:\n 1024:\n name_short_singular: kB\n name_short_plural: kB\n name_long_singular: kilobyte\n name_long_plural: kilobytes\n # ...\n
"},{"location":"arcaflow/workflows/output/","title":"Writing workflow outputs","text":"Outputs in Arcaflow serve a dual purpose:
- They provide the desired resulting data from steps and inputs to STDOUT.
- They allow for the conditional pass/fail state of the workflow (if any defined output is not available, the workflow reports a failure).
You can define an output simply with expressions. Outputs generally include desired output parameters from individual steps, but may also include data from inputs or even static values.
output:\n some_key:\n some_other_key: !expr $.steps.some_step.outputs.success.some_value\n foo: !expr $.inputs.bar\n arca: \"flow\"\n
"},{"location":"arcaflow/workflows/output/#writing-multiple-outputs","title":"Writing multiple outputs","text":"Arcaflow can produce multiple output groups for a workflow. These output groups are mutually exclusive to each other.
A common example of two mutually exclusive events could be the availability of your data storage service. Let\u2019s assume the service is either available, or unavailable (the unavailable state also includes any states where an error is thrown during data insertion). Multiple workflow outputs allows you to plan for these two events.
In this example taken from the Arcaflow Workflows project, the success
output collects the data from the specified steps and inserts it into data storage. The no-indexing
output collects the data, the error logs, and does not store the data.
outputs:\n success:\n pcp: !expr $.steps.pcp.outputs.success\n sysbench: !expr $.steps.sysbench.outputs.success\n metadata: !expr $.steps.metadata.outputs.success\n opensearch: !expr $.steps.opensearch.outputs.success\n no-indexing:\n pcp: !expr $.steps.pcp.outputs.success\n sysbench: !expr $.steps.sysbench.outputs.success\n metadata: !expr $.steps.metadata.outputs.success\n no-index: !expr $.steps.opensearch.outputs.error\n
"},{"location":"arcaflow/workflows/schemas/","title":"Workflow Schemas","text":""},{"location":"arcaflow/workflows/schemas/#schema-names","title":"Schema Names","text":""},{"location":"arcaflow/workflows/schemas/#scalar-names","title":"Scalar Names","text":"Schemas that are not composed within an ObjectSchema do not have an Object ID
. They use a stringified version of their TypeID
for their schema name.
schema name IntEnum enum_integer StringEnum enum_string String string Pattern pattern Int integer Float float Bool bool Map map OneOfString one_of_string OneOfInt one_of_int The name of a ListSchema
is the name of the schema of its element type prefixed with list_
. For lists of lists, the schema name is the name of the inner list schema prefixed with an additional list_
.
"},{"location":"arcaflow/workflows/schemas/#list-names","title":"List Names","text":"schema name List[String] list_string List[Pattern] list_pattern List[StringEnum] list_enum_string List[Map] list_map List[OneOfInt] list_one_of_int List[List[String]] list_list_string List[List[List[String]]] list_list_list_string"},{"location":"arcaflow/workflows/schemas/#object-names","title":"Object Names","text":"The name of an ObjectSchema
is its Object ID
. A ListSchema
that has an ObjectSchema
as its item value uses the name of that ObjectSchema
.
schema object id name Object MyFirstObject MyFirstObject List[Object] MyFirstObject list_MyFirstObject List[List[Object]] MyFirstObject list_list_MyFirstObject List[List[List[Object]]] MyFirstObject list_list_list_MyFirstObject"},{"location":"arcaflow/workflows/schemas/#other-schemas","title":"Other Schemas","text":" ScopeSchema
s do not use a schema name. RefSchema
s use the schema name of the type to which they point.
"},{"location":"arcaflow/workflows/schemas/#generated-combined-schema-names","title":"Generated Combined Schema Names","text":"The name of the schema for the value returned by a given call to bindConstants()
is generated from the names of the schemas of the parameters to the call. Because the output of this function is always a list, the list_
prefix is omitted from the schema name, and only the schema name of the list\u2019s items is used. The name is formed by concatenating the name of the schema of the first parameter\u2019s list items with the name of the schema of the second parameter, separated by a double underscore __
.
first schema second schema name List[Int] Object(ID=\u201dMyFirstObject\u201d) integer__MyFirstObject List[Object(ID=\u201dMyFirstObject\u201d) ] Object(ID=\u201dConstants\u201d) MyFirstObject__Constants List[Object(ID=\u201dMyFirstObject\u201d) ] String MyFirstObject__string List[String] Int string__integer List[String] List[Object(ID=\u201dConstants\u201d)] string__list_Constants"},{"location":"arcaflow/workflows/step/","title":"Writing workflow steps","text":"If your input is complete, you can now turn to writing your workflow steps. You can connect workflow steps by using expressions. For example, if step A
has an input that needs data from step B
, Arcaflow will automatically run step B
first.
To define a step type, you can do the following:
workflow.yamlversion: v0.2.0\nsteps:\n step_a: # Specify any ID here you want to reference the step by\n plugin: \n deployment_type: image\n src: quay.io/some/container/image # This must be an Arcaflow-compatible image\n input: # specify input values as a data structure, mixing in expressions as needed\n some:\n key: !expr $.steps.step_b.outputs.success.some_value \n step_b:\n plugin: \n deployment_type: image\n src: quay.io/some/container/image\n input:\n some:\n key: !expr $.input.some_value # Reference an input value\n
"},{"location":"arcaflow/workflows/step/#plugin-steps","title":"Plugin steps","text":"Plugin steps run Arcaflow plugins in containers. They can use Docker, Podman, or Kubernetes as deployers. If no deployer is specified in the workflow, the plugin will use the local deployer.
Plugin steps have the following properties:
Property Description plugin
Full name of the container image to run. This must be an Arcaflow-compatible container image. step
If a plugin provides more than one possible step, you can specify the step ID here. deploy
Configuration for the deployer. (See below.) This can contain expressions, so you can dynamically specify deployment parameters. input
Input data for the plugin. This can contain expressions, so you can dynamically define inputs. You can reference plugin outputs in the format of $.steps.your_step_id.outputs.your_plugin_output_id.some_variable
.
"},{"location":"arcaflow/workflows/step/#deployers","title":"Deployers","text":"The deploy
key for plugins lets you control how the plugin container is deployed. You can use expressions to use other plugins (e.g. the kubeconfig plugin) to generate the deployment configuration and feed it into other steps.
DockerPodmanKubernetes You can configure the Docker deployer like this:
version: v0.2.0\nstep:\n your_step_id:\n plugin: ...\n input: ...\n deploy: # You can use expressions here\n deployer_name: docker\n connection:\n # Change this to point to a TCP-based Docker socket\n host: host-to-docker\n # Add a certificates here. This is usually needed in TCP mode.\n cacert: |\n Add your CA cert PEM here\n cert: |\n Add your client cert PEM here.\n key: |\n Add your client key PEM here.\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n network:\n # Add your network config here\n platform:\n # Add your platform config here\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Docker deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Connection (object
) Type: object
Properties cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify the Dockerd server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name for Dockerd. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z0-9./:_-]+$
Default\"npipe:////./pipe/docker_engine\"\n
Examples 'unix:///var/run/docker.sock'\n
\u201d
'npipe:////./pipe/docker_engine'\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against the Dockerd with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (map[string, string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No Key type Type: string
Minimum: 1 Maximum: 255 Must match pattern: ^[A-Z0-9_]+$
Value type Type: string
Maximum: 32760 Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
network (reference[NetworkConfig]
) Name: Network configuration Description: Provides information about the container networking for the plugin. Required: No Referenced object: NetworkConfig (see in the Objects section below) platform (reference[PlatformConfig]
) Name: Platform configuration Description: Provides information about the container host platform for the plugin. Required: No Referenced object: PlatformConfig (see in the Objects section below) HostConfig (object
) Type: object
Properties CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) NetworkConfig (object
) Type: object
Properties None
PlatformConfig (object
) Type: object
Properties None
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
If you want to use Podman as your local deployer, you can do so like this:
version: v0.2.0\nstep:\n your_step_id:\n plugin: ...\n input: ...\n deploy: # You can use expressions here\n deployer_name: podman\n podman:\n # Change where Podman is. (You can use this to point to a shell script\n path: /path/to/your/podman\n # Change the network mode\n networkMode: host\n deployment:\n # For more options here see: https://docs.docker.com/engine/api/v1.42/#tag/Container/operation/ContainerCreate\n container:\n # Add your container config here.\n host:\n # Add your host config here.\n imagePullPolicy: Always|IfNotPresent|Never\n timeouts:\n # HTTP timeout\n http: 5s\n
All options for the Podman deployer Type: scope
Root object: Config Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) Objects Config (object
) Type: object
Properties deployment (reference[Deployment]
) Name: Deployment Description: Deployment configuration for the plugin. Required: No Referenced object: Deployment (see in the Objects section below) podman (reference[Podman]
) Name: Podman Description: Podman CLI configuration Required: No Referenced object: Podman (see in the Objects section below) ContainerConfig (object
) Type: object
Properties Domainname (string
) Name: Domain name Description: Domain name for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
Env (list[string]
) Name: Environment variables Description: Environment variables to set on the plugin container. Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+=.+$
Hostname (string
) Name: Hostname Description: Hostname for the plugin container. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-zA-Z0-9-_.]+$
MacAddress (string
) Name: MAC address Description: Media Access Control address for the container. Required: No Must match pattern: ^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$
NetworkDisabled (bool
) Name: Disable network Description: Disable container networking completely. Required: No User (string
) Name: Username Description: User that will run the command inside the container. Optionally, a group can be specified in the user:group format. Required: No Minimum: 1 Maximum: 255 Must match pattern: ^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$
Deployment (object
) Type: object
Properties container (reference[ContainerConfig]
) Name: Container configuration Description: Provides information about the container for the plugin. Required: No Referenced object: ContainerConfig (see in the Objects section below) host (reference[HostConfig]
) Name: Host configuration Description: Provides information about the container host for the plugin. Required: No Referenced object: HostConfig (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Image pull policy Description: When to pull the plugin image. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
HostConfig (object
) Type: object
Properties Binds (list[string]
) Name: Volume Bindings Description: Volumes Required: No List Items Type: string
Minimum: 1 Maximum: 32760 Must match pattern: ^.+:.+$
CapAdd (list[string]
) Name: Add capabilities Description: Add capabilities to the container. Required: No List Items Type: string
CapDrop (list[string]
) Name: Drop capabilities Description: Drop capabilities from the container. Required: No List Items Type: string
CgroupnsMode (enum[string]
) Name: CGroup namespace mode Description: CGroup namespace mode to use for the container. Required: No Values - `` Empty
host
Host private
Private
Dns (list[string]
) Name: DNS servers Description: DNS servers to use for lookup. Required: No List Items Type: string
DnsOptions (list[string]
) Name: DNS options Description: DNS options to look for. Required: No List Items Type: string
DnsSearch (list[string]
) Name: DNS search Description: DNS search domain. Required: No List Items Type: string
ExtraHosts (list[string]
) Name: Extra hosts Description: Extra hosts entries to add Required: No List Items Type: string
NetworkMode (string
) Name: Network mode Description: Specifies either the network mode, the container network to attach to, or a name of a Docker network to use. Required: No Must match pattern: ^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$
Examples \"none\"\n
\u201d
\"bridge\"\n
\u201d \"host\"\n
\u201d \"container:container-name\"\n
\u201d \"network-name\"\n
PortBindings (map[string, list[reference[PortBinding]]]
) Name: Port bindings Description: Ports to expose on the host machine. Ports are specified in the format of portnumber/protocol. Required: No Key type Type: string
Must match pattern: ^[0-9]+(/[a-zA-Z0-9]+)$
Value type Type: list[reference[PortBinding]]
List Items Type: reference[PortBinding]
Referenced object: PortBinding (see in the Objects section below) Podman (object
) Type: object
Properties cgroupNs (string
) Name: CGroup namespace Description: Provides the Cgroup Namespace settings for the container Required: No Must match pattern: ^host|ns:/proc/\\d+/ns/cgroup|container:.+|private$
containerName (string
) Name: Container Name Description: Provides name of the container Required: No Must match pattern: ^.*$
imageArchitecture (string
) Name: Podman image Architecture Description: Provides Podman Image Architecture Required: No Must match pattern: ^.*$
Default\"amd64\"\n
imageOS (string
) Name: Podman Image OS Description: Provides Podman Image Operating System Required: No Must match pattern: ^.*$
Default\"linux\"\n
networkMode (string
) Name: Network Mode Description: Provides network settings for the container Required: No Must match pattern: ^bridge:.*|host|none$
path (string
) Name: Podman path Description: Provides the path of podman executable Required: No Must match pattern: ^.*$
Default\"podman\"\n
PortBinding (object
) Type: object
Properties HostIP (string
) Name: Host IP Required: No HostPort (string
) Name: Host port Required: No Must match pattern: ^0-9+$
The Kubernetes deployer deploys on top of Kubernetes. You can set up the deployer like this:
version: v0.2.0\nstep:\n your_step_id:\n plugin: ...\n input: ...\n deploy: # You can use expressions here\n deployer_name: kubernetes\n connection:\n host: localhost:6443\n cert: |\n Add your client cert in PEM format here.\n key: |\n Add your client key in PEM format here.\n cacert: |\n Add the server CA cert in PEM format here.\n
All options for the Kubernetes deployer Type: scope
Root object: Config Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) Objects AWSElasticBlockStoreVolumeSource (object
) Type: object
Properties None
AzureDiskVolumeSource (object
) Type: object
Properties None
AzureFileVolumeSource (object
) Type: object
Properties None
CSIVolumeSource (object
) Type: object
Properties None
CephFSVolumeSource (object
) Type: object
Properties None
CinderVolumeSource (object
) Type: object
Properties None
Config (object
) Type: object
Properties connection (reference[Connection]
) Name: Connection Description: Docker connection information. Required: No Referenced object: Connection (see in the Objects section below) pod (reference[Pod]
) Name: Pod Description: Pod configuration for the plugin. Required: No Referenced object: Pod (see in the Objects section below) timeouts (reference[Timeouts]
) Name: Timeouts Description: Timeouts for the Docker connection. Required: No Referenced object: Timeouts (see in the Objects section below) ConfigMapVolumeSource (object
) Type: object
Properties None
Connection (object
) Type: object
Properties bearerToken (string
) Name: Bearer token Description: Bearer token to authenticate against the Kubernetes API with. Required: No burst (int
) Name: Burst Description: Burst value for query throttling. Required: No Minimum: 0 Default10\n
cacert (string
) Name: CA certificate Description: CA certificate in PEM format to verify Kubernetes server certificate against. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
cert (string
) Name: Client certificate Description: Client certificate in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN CERTIFICATE-----(\\s*.*\\s*)*-----END CERTIFICATE-----\\s*$
Examples \"-----BEGIN CERTIFICATE-----\\nMIIB4TCCAYugAwIBAgIUCHhhffY1lzezGatYMR02gpEJChkwDQYJKoZIhvcNAQEL\\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA5MjgwNTI4MTJaFw0yMzA5\\nMjgwNTI4MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\\nAANLADBIAkEArr89f2kggSO/yaCB6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1\\nnEiPnLbzDDgMU8KCPAMhI7JpYRlHnipxWwIDAQABo1MwUTAdBgNVHQ4EFgQUiZ6J\\nDwuF9QCh1vwQGXs2MutuQ9EwHwYDVR0jBBgwFoAUiZ6JDwuF9QCh1vwQGXs2Mutu\\nQ9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAFYIFM27BDiG725d\\nVkhRblkvZzeRHhcwtDOQTC9d8M/LymN2y0nHSlJCZm/Lo/aH8viSY1vi1GSHfDz7\\nTlfe8gs=\\n-----END CERTIFICATE-----\\n\"\n
host (string
) Name: Host Description: Host name and port of the Kubernetes server Required: No Default\"kubernetes.default.svc\"\n
key (string
) Name: Client key Description: Client private key in PEM format to authenticate against Kubernetes with. Required: No Minimum: 1 Must match pattern: ^\\s*-----BEGIN ([A-Z]+) PRIVATE KEY-----(\\s*.*\\s*)*-----END ([A-Z]+) PRIVATE KEY-----\\s*$
Examples \"-----BEGIN PRIVATE KEY-----\\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEArr89f2kggSO/yaCB\\n6EwIQeT6ZptBoX0ZvCMI+DpkCwqOS5fwRbj1nEiPnLbzDDgMU8KCPAMhI7JpYRlH\\nnipxWwIDAQABAkBybu/x0MElcGi2u/J2UdwScsV7je5Tt12z82l7TJmZFFJ8RLmc\\nrh00Gveb4VpGhd1+c3lZbO1mIT6v3vHM9A0hAiEA14EW6b+99XYza7+5uwIDuiM+\\nBz3pkK+9tlfVXE7JyKsCIQDPlYJ5xtbuT+VvB3XOdD/VWiEqEmvE3flV0417Rqha\\nEQIgbyxwNpwtEgEtW8untBrA83iU2kWNRY/z7ap4LkuS+0sCIGe2E+0RmfqQsllp\\nicMvM2E92YnykCNYn6TwwCQSJjRxAiEAo9MmaVlK7YdhSMPo52uJYzd9MQZJqhq+\\nlB1ZGDx/ARE=\\n-----END PRIVATE KEY-----\\n\"\n
password (string
) Name: Password Description: Password for basic authentication. Required: No path (string
) Name: Path Description: Path to the API server. Required: No Default\"/api\"\n
qps (float
) Name: QPS Description: Queries Per Second allowed against the API. Required: No Minimum: 0 Units: queries Default5.0\n
serverName (string
) Name: TLS server name Description: Expected TLS server name to verify in the certificate. Required: No username (string
) Name: Username Description: Username for basic authentication. Required: No Container (object
) Type: object
Properties args (list[string]
) Name: Arguments Description: Arguments to the entypoint (command). Required: No List Items Type: string
command (list[string]
) Name: Command Description: Override container entry point. Not executed with a shell. Required: No Minimum items: 1 List Items Type: string
env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) image (string
) Name: Image Description: Container image to use for this container. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9_\\-:./]+$
imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 workingDir (string
) Name: Working directory Description: Override the container working directory. Required: No DownwardAPIVolumeSource (object
) Type: object
Properties None
EmptyDirVolumeSource (object
) Type: object
Properties medium (string
) Name: Medium Description: How to store the empty directory Required: No Minimum: 1 Must match pattern: ^(|Memory|HugePages|HugePages-.*)$
EnvFromSource (object
) Type: object
Properties configMapRef (object
) Name: Config map source Description: Populates the source from a config map. Required: No Properties name (string
) Name: Name Description: Name of the referenced config map. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the config map must be defined. Required: No prefix (string
) Name: Prefix Description: An optional identifier to prepend to each key in the ConfigMap. Required: No Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
secretRef (object
) Name: Secret source Description: Populates the source from a secret. Required: No Properties name (string
) Name: Name Description: Name of the referenced secret. Required: Yes Minimum: 1 optional (bool
) Name: Optional Description: Specify whether the secret must be defined. Required: No EphemeralVolumeSource (object
) Type: object
Properties None
FCVolumeSource (object
) Type: object
Properties None
FlexVolumeSource (object
) Type: object
Properties None
FlockerVolumeSource (object
) Type: object
Properties None
GCEPersistentDiskVolumeSource (object
) Type: object
Properties None
GlusterfsVolumeSource (object
) Type: object
Properties None
HostPathVolumeSource (object
) Type: object
Properties path (string
) Name: Path Description: Path to the directory on the host. Required: Yes Minimum: 1 Examples \"/srv/volume1\"\n
type (enum[string]
) Name: Type Description: Type of the host path. Required: No Values - `` Unset
BlockDevice
Block device CharDevice
Character device Directory
Directory DirectoryOrCreate
Create directory if not found File
File FileOrCreate
Create file if not found Socket
Socket
ISCSIVolumeSource (object
) Type: object
Properties None
NFSVolumeSource (object
) Type: object
Properties None
ObjectMeta (object
) Type: object
Properties annotations (map[string, string]
) Name: Annotations Description: Kubernetes annotations to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
generateName (string
) Name: Name prefix Description: Name prefix to generate pod names from. Required: No labels (map[string, string]
) Name: Labels Description: Kubernetes labels to appy. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for details. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
name (string
) Name: Name Description: Pod name. Required: No namespace (string
) Name: Namespace Description: Kubernetes namespace to deploy in. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"default\"\n
PersistentVolumeClaimVolumeSource (object
) Type: object
Properties None
PhotonPersistentDiskVolumeSource (object
) Type: object
Properties None
Pod (object
) Type: object
Properties metadata (reference[ObjectMeta]
) Name: Metadata Description: Pod metadata. Required: No Referenced object: ObjectMeta (see in the Objects section below) spec (reference[PodSpec]
) Name: Specification Description: Pod specification. Required: No Referenced object: PodSpec (see in the Objects section below) PodSpec (object
) Type: object
Properties affinity (object
) Name: Affinity rules Description: Affinity rules. Required: No Properties podAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
podAntiAffinity (object
) Name: Pod Affinity Description: The pod affinity rules. Required: No Properties requiredDuringSchedulingIgnoredDuringExecution (list[object]
) Name: Required During Scheduling Ignored During Execution Description: Hard pod affinity rules. Required: No Minimum items: 1 List Items Type: object
Properties labelSelector (object
) Name: MatchExpressions Description: Expressions for the label selector. Required: No Properties matchExpressions (list[object]
) Name: MatchExpression Description: Expression for the label selector. Required: No Minimum items: 1 List Items Type: object
Properties key (string
) Name: Key Description: Key for the label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
operator (string
) Name: Operator Description: Logical operator for Kubernetes to use when interpreting the rules. You can use In, NotIn, Exists, DoesNotExist, Gt and Lt. Required: No Maximum: 253 Must match pattern: In|NotIn|Exists|DoesNotExist|Gt|Lt
values (list[string]
) Name: Values Description: Values for the label that the system uses to denote the domain. Required: No Minimum items: 1 List Items Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
topologyKey (string
) Name: TopologyKey Description: Key for the node label that the system uses to denote the domain. Required: No Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_./][a-zA-Z0-9]+)*[a-zA-Z0-9])$
containers (list[reference[Container]]
) Name: Containers Description: A list of containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) initContainers (list[reference[Container]]
) Name: Init containers Description: A list of initialization containers belonging to the pod. Required: No List Items Type: reference[Container]
Referenced object: Container (see in the Objects section below) nodeSelector (map[string, string]
) Name: Labels Description: Node labels you want the target node to have. Required: No Key type Type: string
Must match pattern: ^(|([a-zA-Z](|[a-zA-Z\\-.]{0,251}[a-zA-Z0-9]))/)([a-zA-Z](|[a-zA-Z\\\\-]{0,61}[a-zA-Z0-9]))$
Value type Type: string
Maximum: 63 Must match pattern: ^(|[a-zA-Z0-9]+(|[-_.][a-zA-Z0-9]+)*[a-zA-Z0-9])$
pluginContainer (object
) Name: Plugin container Description: The container to run the plugin in. Required: Yes Properties env (list[object]
) Name: Environment Description: Environment variables for this container. Required: No List Items Type: object
Properties name (string
) Name: Name Description: Environment variables name. Required: Yes Minimum: 1 Must match pattern: ^[a-zA-Z0-9-._]+$
value (string
) Name: Value Description: Value for the environment variable. Required: No valueFrom (reference[EnvFromSource]
) Name: Value source Description: Load the environment variable from a secret or config map. Required: No Referenced object: EnvFromSource (see in the Objects section below) envFrom (list[reference[EnvFromSource]]
) Name: Environment sources Description: List of sources to populate the environment variables from. Required: No List Items Type: reference[EnvFromSource]
Referenced object: EnvFromSource (see in the Objects section below) imagePullPolicy (enum[string]
) Name: Volume device Description: Mount a raw block device within the container. Required: No Values Always
Always IfNotPresent
If not present Never
Never
Default\"IfNotPresent\"\n
name (string
) Name: Name Description: Name for the container. Each container in a pod must have a unique name. Required: No Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
Default\"arcaflow-plugin-container\"\n
securityContext (object
) Name: Volume device Description: Mount a raw block device within the container. Required: No Properties capabilities (object
) Name: Capabilities Description: Add or drop POSIX capabilities. Required: No Properties add (list[string]
) Name: Add Description: Add POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
drop (list[string]
) Name: Drop Description: Drop POSIX capabilities. Required: No List Items Type: string
Minimum: 1 Must match pattern: ^[A-Z_]+$
privileged (bool
) Name: Privileged Description: Run the container in privileged mode. Required: No volumeDevices (list[object]
) Name: Volume device Description: Mount a raw block device within the container. Required: No List Items Type: object
Properties devicePath (string
) Name: Device path Description: Path inside the container the device will be mapped to. Required: Yes Minimum: 1 name (string
) Name: Name Description: Must match the persistent volume claim in the pod. Required: Yes Minimum: 1 volumeMounts (list[object]
) Name: Volume mounts Description: Pod volumes to mount on this container. Required: No List Items Type: object
Properties mountPath (string
) Name: Mount path Description: Path to mount the volume on inside the container. Required: Yes Minimum: 1 name (string
) Name: Volume name Description: Must match the pod volume to mount. Required: Yes Minimum: 1 readOnly (bool
) Name: Read only Description: Mount volume as read-only. Required: No Defaultfalse\n
subPath (string
) Name: Subpath Description: Path from the volume to mount. Required: No Minimum: 1 volumes (list[reference[Volume]]
) Name: Volumes Description: A list of volumes that can be mounted by containers belonging to the pod. Required: No List Items Type: reference[Volume]
Referenced object: Volume (see in the Objects section below) PortworxVolumeSource (object
) Type: object
Properties None
ProjectedVolumeSource (object
) Type: object
Properties None
QuobyteVolumeSource (object
) Type: object
Properties None
RBDVolumeSource (object
) Type: object
Properties None
ScaleIOVolumeSource (object
) Type: object
Properties None
SecretVolumeSource (object
) Type: object
Properties None
StorageOSVolumeSource (object
) Type: object
Properties None
Timeouts (object
) Type: object
Properties http (int
) Name: HTTP Description: HTTP timeout for the Docker API. Required: No Minimum: 100000000 Units: nanoseconds Default\"15s\"\n
Volume (object
) Type: object
Properties awsElasticBlockStore (reference[AWSElasticBlockStoreVolumeSource]
) Name: AWS EBS Description: AWS Elastic Block Storage. Required: No Referenced object: AWSElasticBlockStoreVolumeSource (see in the Objects section below) azureDisk (reference[AzureDiskVolumeSource]
) Name: Azure Data Disk Description: Mount an Azure Data Disk as a volume. Required: No Referenced object: AzureDiskVolumeSource (see in the Objects section below) azureFile (reference[AzureFileVolumeSource]
) Name: Azure File Description: Mount an Azure File Service mount. Required: No Referenced object: AzureFileVolumeSource (see in the Objects section below) cephfs (reference[CephFSVolumeSource]
) Name: CephFS Description: Mount a CephFS volume. Required: No Referenced object: CephFSVolumeSource (see in the Objects section below) cinder (reference[CinderVolumeSource]
) Name: Cinder Description: Mount a cinder volume attached and mounted on the host machine. Required: No Referenced object: CinderVolumeSource (see in the Objects section below) configMap (reference[ConfigMapVolumeSource]
) Name: ConfigMap Description: Mount a ConfigMap as a volume. Required: No Referenced object: ConfigMapVolumeSource (see in the Objects section below) csi (reference[CSIVolumeSource]
) Name: CSI Volume Description: Mount a volume using a CSI driver. Required: No Referenced object: CSIVolumeSource (see in the Objects section below) downwardAPI (reference[DownwardAPIVolumeSource]
) Name: Downward API Description: Specify a volume that the pod should mount itself. Required: No Referenced object: DownwardAPIVolumeSource (see in the Objects section below) emptyDir (reference[EmptyDirVolumeSource]
) Name: Empty directory Description: Temporary empty directory. Required: No Referenced object: EmptyDirVolumeSource (see in the Objects section below) ephemeral (reference[EphemeralVolumeSource]
) Name: Ephemeral Description: Mount a volume that is handled by a cluster storage driver. Required: No Referenced object: EphemeralVolumeSource (see in the Objects section below) fc (reference[FCVolumeSource]
) Name: Fibre Channel Description: Mount a Fibre Channel volume that's attached to the host machine. Required: No Referenced object: FCVolumeSource (see in the Objects section below) flexVolume (reference[FlexVolumeSource]
) Name: Flex Description: Mount a generic volume provisioned/attached using an exec based plugin. Required: No Referenced object: FlexVolumeSource (see in the Objects section below) flocker (reference[FlockerVolumeSource]
) Name: Flocker Description: Mount a Flocker volume. Required: No Referenced object: FlockerVolumeSource (see in the Objects section below) gcePersistentDisk (reference[GCEPersistentDiskVolumeSource]
) Name: GCE disk Description: Google Cloud disk. Required: No Referenced object: GCEPersistentDiskVolumeSource (see in the Objects section below) glusterfs (reference[GlusterfsVolumeSource]
) Name: GlusterFS Description: Mount a Gluster volume. Required: No Referenced object: GlusterfsVolumeSource (see in the Objects section below) hostPath (reference[HostPathVolumeSource]
) Name: Host path Description: Mount volume from the host. Required: No Referenced object: HostPathVolumeSource (see in the Objects section below) iscsi (reference[ISCSIVolumeSource]
) Name: iSCSI Description: Mount an iSCSI volume. Required: No Referenced object: ISCSIVolumeSource (see in the Objects section below) name (string
) Name: Name Description: The name this volume can be referenced by. Required: Yes Maximum: 253 Must match pattern: ^[a-z0-9]($|[a-z0-9\\-_]*[a-z0-9])$
nfs (reference[NFSVolumeSource]
) Name: NFS Description: Mount an NFS share. Required: No Referenced object: NFSVolumeSource (see in the Objects section below) persistentVolumeClaim (reference[PersistentVolumeClaimVolumeSource]
) Name: Persistent Volume Claim Description: Mount a Persistent Volume Claim. Required: No Referenced object: PersistentVolumeClaimVolumeSource (see in the Objects section below) photonPersistentDisk (reference[PhotonPersistentDiskVolumeSource]
) Name: PhotonController persistent disk Description: Mount a PhotonController persistent disk as a volume. Required: No Referenced object: PhotonPersistentDiskVolumeSource (see in the Objects section below) portworxVolume (reference[PortworxVolumeSource]
) Name: Portworx Volume Description: Mount a Portworx volume. Required: No Referenced object: PortworxVolumeSource (see in the Objects section below) projected (reference[ProjectedVolumeSource]
) Name: Projected Description: Projected items for all in one resources secrets, configmaps, and downward API. Required: No Referenced object: ProjectedVolumeSource (see in the Objects section below) quobyte (reference[QuobyteVolumeSource]
) Name: quobyte Description: Mount Quobyte volume from the host. Required: No Referenced object: QuobyteVolumeSource (see in the Objects section below) rbd (reference[RBDVolumeSource]
) Name: Rados Block Device Description: Mount a Rados Block Device. Required: No Referenced object: RBDVolumeSource (see in the Objects section below) scaleIO (reference[ScaleIOVolumeSource]
) Name: ScaleIO Persistent Volume Description: Mount a ScaleIO persistent volume. Required: No Referenced object: ScaleIOVolumeSource (see in the Objects section below) secret (reference[SecretVolumeSource]
) Name: Secret Description: Mount a Kubernetes secret. Required: No Referenced object: SecretVolumeSource (see in the Objects section below) storageos (reference[StorageOSVolumeSource]
) Name: StorageOS Volume Description: Mount a StorageOS volume. Required: No Referenced object: StorageOSVolumeSource (see in the Objects section below) vsphereVolume (reference[VsphereVirtualDiskVolumeSource]
) Name: vSphere Virtual Disk Description: Mount a vSphere Virtual Disk as a volume. Required: No Referenced object: VsphereVirtualDiskVolumeSource (see in the Objects section below) VsphereVirtualDiskVolumeSource (object
) Type: object
Properties None
"},{"location":"arcaflow/workflows/versioning/","title":"Workflow schema versions","text":""},{"location":"arcaflow/workflows/versioning/#valid-version-string","title":"Valid version string","text":"All workflow schema versions conform to semantic version 2.0.0 with a major, minor, and patch version. In this document, since the prepended v
is unnecessary it is not used. However, it is required as a value for the version key in your workflow file.
Invalid version string for workflow.yaml
.
version: 0.2.0\ninput:\nsteps:\noutputs:\n
Valid version string for workflow.yaml
.
version: v0.2.0\ninput:\nsteps:\noutputs:\n
"},{"location":"arcaflow/workflows/versioning/#supported-versions","title":"Supported versions","text":" - 0.2.0
"},{"location":"arcaflow/workflows/versioning/#compatibility-matrix","title":"Compatibility Matrix","text":"Workflow schema version Arcaflow Engine release 0.2.0 0.9.0"},{"location":"arcaflow/workflows/versioning/#upgrading","title":"Upgrading","text":""},{"location":"arcaflow/workflows/versioning/#020","title":"0.2.0","text":"For the configuration file, config.yaml
, two types of deployers are now possible, image
and python
, so deployer
has become deployers
. Effectively, the type
key has become the deployer_name
key. The deployer_name
key and value are required which means you must also have either the image
key or the python
key.
deployers:\n image:\n deployer_name: docker|podman|kubernetes\n python:\n deployer_name: python\n
For your workflow file, workflow.yaml
, the version
key and value are required, and they must be at the root of the file.
version: v0.2.0\ninputs: {}\nsteps: {}\noutputs: {}\n
"},{"location":"arcalog/","title":"Arcalog: Assisted Root Cause Analysis for Your Logs","text":"Arcalog is still in early development. A scientific paper describing the project is available as a preprint.
The README contains a guide on how to use Arcalog to gather data as well as how to use the --http
flag to run a minimal user interface for downloading individual build IDs from Prow.
Pre-release developer documentation is also available if you want to use the early pre-release version of Arcalog to embed the data gathering steps into your own application.
"}]}
\ No newline at end of file
diff --git a/sitemap.xml b/sitemap.xml
index ca6816b..8e092a4 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -2,158 +2,158 @@
https://arcalot.io/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/getting-started/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/concepts/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/concepts/architecture/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/concepts/plugins/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/concepts/typing/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/concepts/workflows/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/contributing/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/contributing/deployers/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/contributing/engine/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/contributing/expressions/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/contributing/plugin-protocol/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/contributing/typing/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/packaging/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/go/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/go/first/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/go/schema/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/data-model/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/embedding/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/faq/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/first/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/official/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/schema/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/plugins/python/testing/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/running/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/running/running/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/running/setup/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/expressions/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/flow-control/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/input/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/output/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/schemas/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/step/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcaflow/workflows/versioning/
- 2024-09-09
+ 2024-09-23
https://arcalot.io/arcalog/
- 2024-09-09
+ 2024-09-23
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index 9e45453144296762452ae28ec48aaa9472e860d2..cf8fd19a2195bd4c9e68a3923d3238fe61986104 100644
GIT binary patch
literal 427
zcmV;c0aX4UiwFpSukdC9|8r?{Wo=<_E_iKh0L_@qZi6rk$M1a##2x6m9oC7`+n!*1
zfHWA08saQ*M*H>)g^nF3O~T^3*{f?imkqiYKmyEI5XdKpswLlc-
zH1i}JLA@?qqLE<9Gf|_Zi#tM6aKI$N>d-2~H34+BaqXGl1G@8QS%2{4)F}|h$QN=A
zR^v71H%1L$;@$&J0T=7Qk13vW8m1V`tV2SHno$WyJhEh6Vx@^?w?gO;)9pZ7YVZhY#Jq%;N1-WAS4O=`c;t&
z#;`$I;qk{c*mcoNHTQyLtN0cP<2}2TU2E5Riw0$EATBrtVx-^CVz}@GY%w<(MkXS
literal 427
zcmV;c0aX4UiwFn+b?0UR|8r?{Wo=<_E_iKh0L_@qZi6rk$M1a##2wOB)3i>M-u49B
z1Ej$~v=C>B8SUFI6gqaCGzpIb1QYr9&xb9O;&kau@(Dg@w8wOpZ_@;kpp1KKH~i@`YT1
zGkA^pjZp*Gxc7ilz{Pn8V~XdThA9R!=aEpMVN}8qk1QGJ4s}w!%Z6>t4YF}-?E|sV
zr-F!S&9Bs=Wo1{AWETB`VU+>h8LEnH_s=S7`N7NQi-cJL*0okz@@|HI5RwF8{i?|K
z*0Moa;qk|{ck7~=YVHNgR`D$o#(Q=vyVkDt77g0Ep19!H6C?e87Q=-nV2io=IBU&>
VWAG2G+aqSF@(a?%C#;SW002S%({lg-