Source code for hansken_extraction_plugin.api.data_context
+"""This module contains the definition of the DataContext."""
+fromdataclassesimportdataclass
+
+
+
+[docs]
+@dataclass(frozen=True)
+classDataContext:
+"""This class contains the data context of a plugin that is processing a trace."""
+
+ data_type:str#: the named data type that is being processed
+ data_size:int#: the size / total length of the data stream that is being processed
+
+ def__eq__(self,other):
+ # override the default equality check, a subclass is considered equal as long as it matches all the fields this
+ # type describes
+ returnisinstance(other,DataContext)and(self.data_type,self.data_size)==(other.data_type,other.data_size)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/extraction_plugin.html b/0.9.16/_modules/hansken_extraction_plugin/api/extraction_plugin.html
new file mode 100644
index 0000000..7625ff9
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/extraction_plugin.html
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.extraction_plugin — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.extraction_plugin
+"""
+This module contains the different types of Extraction Plugins.
+
+The types of Extraction Plugins differ in their process functions.
+"""
+fromabcimportABC,abstractmethod
+importinspect
+fromtypingimportList
+
+fromhansken_extraction_plugin.api.data_contextimportDataContext
+fromhansken_extraction_plugin.api.extraction_traceimportExtractionTrace,MetaExtractionTrace
+fromhansken_extraction_plugin.api.plugin_infoimportPluginInfo
+fromhansken_extraction_plugin.api.trace_searcherimportTraceSearcher
+fromhansken_extraction_plugin.api.transformerimportTransformer
+fromhansken_extraction_plugin.decorators.transformerimporttransformer_registry
+
+
+
+[docs]
+classBaseExtractionPlugin(ABC):
+"""All Extraction Plugins are derived from this class."""
+
+
+[docs]
+ @abstractmethod
+ defplugin_info(self)->PluginInfo:
+"""Return information about this extraction plugin."""
+
+
+ @property
+ deftransformers(self)->List[Transformer]:
+"""
+ Dynamically retrieves the transformer methods that were decorated with @transform.
+
+ Note: This method will retrieve transformers for superclasses as well.
+ """
+ # Retrieve all super classes of a plugin so that we can also retrieve transformers of super classes.
+ # Note: This also contains the more specific instance this method might be called on as well.
+ base_classes=inspect.getmro(self.__class__)
+
+ # Check for each (super) class of the plugin if transformers have been registered.
+ transformers=[
+ transformerforclinbase_classes
+ ifcl.__name__intransformer_registry
+ fortransformerintransformer_registry[cl.__name__]
+ ]
+
+ returntransformers
+
+
+
+
+[docs]
+classExtractionPlugin(BaseExtractionPlugin):
+"""Default extraction plugin, that processes a trace and one of its datastreams."""
+
+
+[docs]
+ @abstractmethod
+ defprocess(self,trace:ExtractionTrace,data_context:DataContext):
+"""
+ Process a given trace.
+
+ This method is called for every trace that is processed by this tool.
+
+ :param trace: Trace that is being processed
+ :param data_context: Data data_context describing the data stream that is being processed
+ """
+
+
+
+
+
+[docs]
+classMetaExtractionPlugin(BaseExtractionPlugin):
+"""Extraction Plugin that processes a trace only with its metadata, without processing its data."""
+
+
+[docs]
+ @abstractmethod
+ defprocess(self,trace:MetaExtractionTrace):
+"""
+ Process a given trace.
+
+ This method is called for every trace that is processed by this tool.
+
+ :param trace: Trace that is being processed
+ """
+
+
+
+
+
+[docs]
+classDeferredExtractionPlugin(BaseExtractionPlugin):
+"""
+ Extraction Plugin that can be run at a different extraction stage.
+
+ This type of plugin also allows accessing other traces using the searcher.
+ """
+
+
+[docs]
+ @abstractmethod
+ defprocess(self,trace:ExtractionTrace,data_context:DataContext,searcher:TraceSearcher):
+"""
+ Process a given trace.
+
+ This method is called for every trace that is processed by this tool.
+
+ :param trace: Trace that is being processed
+ :param data_context: Data data_context describing the data stream that is being processed
+ :param searcher: TraceSearcher that can be used to obtain more traces
+ """
+
+
+
+
+
+[docs]
+classDeferredMetaExtractionPlugin(BaseExtractionPlugin):
+"""
+ Extraction Plugin that can be postponed to a later extraction iteration.
+
+ This type of plugin processes a trace only with its metadata,
+ without processing its data and accesses traces using the searcher.
+ """
+
+
+[docs]
+ @abstractmethod
+ defprocess(self,trace:MetaExtractionTrace,searcher:TraceSearcher):
+"""
+ Process a given trace.
+
+ This method is called for every trace that is processed by this tool.
+
+ :param trace: Trace that is being processed
+ :param searcher: TraceSearcher that can be used to obtain more traces
+ """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/extraction_trace.html b/0.9.16/_modules/hansken_extraction_plugin/api/extraction_trace.html
new file mode 100644
index 0000000..2a7f186
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/extraction_trace.html
@@ -0,0 +1,394 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.extraction_trace — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.extraction_trace
+"""
+This module contains the different Trace apis.
+
+Note that there are a couple of different traces:
+ * The ExtractionTrace and MetaExtractionTrace, which are offered to the process function.
+ * ExtractionTraceBuilder, which is a trace that can be built; it does not exist in hansken yet, but it is added after
+ building.
+ * SearchTrace, which represents an immutable trace which is returned after searching for traces.
+"""
+fromabcimportABC,abstractmethod
+fromioimportBufferedReader,BufferedWriter,TextIOBase
+fromtypingimportAny,Literal,Mapping,Optional,Union
+
+fromhansken_extraction_plugin.api.traceletimportTracelet
+fromhansken_extraction_plugin.api.transformationimportTransformation
+
+
+
+[docs]
+classExtractionTraceBuilder(ABC):
+"""
+ ExtractionTrace that can be build.
+
+ Represents child traces.
+ """
+
+
+[docs]
+ @abstractmethod
+ defupdate(self,key_or_updates:Optional[Union[Mapping,str]]=None,value:Optional[Any]=None,
+ data:Optional[Mapping[str,bytes]]=None)->'ExtractionTraceBuilder':
+"""
+ Update or add metadata properties for this `.ExtractionTraceBuilder`.
+
+ Can be used to update the name of the Trace represented by this builder,
+ if not already set.
+
+ :param key_or_updates: either a `str` (the metadata property to be
+ updated) or a mapping supplying both keys and values to be updated
+ :param value: the value to update metadata property *key* to (used
+ only when *key_or_updates* is a `str`, an exception will be thrown
+ if *key_or_updates* is a mapping)
+ :param data: a `dict` mapping data type / stream name to bytes to be
+ added to the trace
+ :return: this `.ExtractionTraceBuilder`
+ """
+
+
+
+[docs]
+ @abstractmethod
+ defadd_tracelet(self,
+ tracelet:Union[Tracelet,str],
+ value:Optional[Mapping[str,Any]]=None)->'ExtractionTraceBuilder':
+"""
+ Add a `.Tracelet` to this `.ExtractionTraceBuilder`.
+
+ :param tracelet: the Tracelet or tracelet type (supplied as a `str`) to add
+ :param value: the tracelet properties to add (only applicable when *tracelet* is a `str`)
+ :return: this `.ExtractionTraceBuilder`
+ """
+
+
+
+[docs]
+ @abstractmethod
+ defadd_transformation(self,data_type:str,transformation:Transformation)->'ExtractionTraceBuilder':
+"""
+ Update or add transformations for this `.ExtractionTraceBuilder`.
+
+ :param data_type: data type of the Transformation
+ :param transformation: the Transformation to add
+ :return: this `.ExtractionTraceBuilder`
+ """
+
+
+
+[docs]
+ @abstractmethod
+ defchild_builder(self,name:Optional[str]=None)->'ExtractionTraceBuilder':
+"""
+ Create a new `.TraceBuilder` to build a child trace to the trace to be represented by this builder.
+
+ .. note::
+ Traces should be created and built in depth first order,
+ parent before child (pre-order).
+
+ :return: a `.TraceBuilder` set up to save a new trace as the child
+ trace of this builder
+ """
+
+
+
+[docs]
+ defadd_data(self,stream:str,data:bytes)->'ExtractionTraceBuilder':
+"""
+ Add data to this trace as a named stream.
+
+ :param stream: name of the data stream to be added
+ :param data: data to be attached
+ :return: this `.ExtractionTraceBuilder`
+ """
+ returnself.update(data={stream:data})
+
+
+
+[docs]
+ @abstractmethod
+ defopen(self,data_type:Optional[str]=None,offset:int=0,size:Optional[int]=None,
+ mode:Literal['rb','wb','w','wt']='rb',encoding='utf-8',buffer_size:Optional[int]=None) \
+ ->Union[BufferedReader,BufferedWriter,TextIOBase]:
+"""
+ Open a data stream to read or write data from or to the `.ExtractionTrace`.
+
+ :param data_type: the data type of the datastream, 'raw' by default
+ :param offset: byte offset to start the stream on when reading
+ :param size: the number of bytes to make available when reading
+ :param mode: 'rb' for reading, 'wb' for writing
+ :param encoding: encoding for writing text, used to convert `str` values to bytes, \
+ only valid for modes 'w' and 'wt'
+ :param buffer_size: buffer size for reading (cache read back/ahead) or writing (cache for flush) data
+ :return: a file-like object to read or write bytes from the named stream
+ """
+
+
+
+[docs]
+ @abstractmethod
+ defbuild(self)->str:
+"""
+ Save the trace being built by this builder to remote.
+
+ .. note::
+ Building more than once will result in an error being raised.
+
+ :return: the new trace' id
+ """
+
+
+
+
+
+[docs]
+classTrace(ABC):
+"""All trace classes should be able to return values."""
+
+
+[docs]
+ @abstractmethod
+ defget(self,key:str,default:Optional[Any]=None)->Any:
+"""
+ Return metadata properties for this `.ExtractionTrace`.
+
+ :param key: the metadata property to be retrieved
+ :param default: value returned if property is not set
+ :return: the value of the requested metadata property
+ """
+
+
+
+
+
+[docs]
+classSearchTrace(Trace):
+"""SearchTraces represent traces returned when searching for traces."""
+
+
+[docs]
+ @abstractmethod
+ defopen(self,stream:str='raw',offset:int=0,size:Optional[int]=None,
+ buffer_size:Optional[int]=None)->BufferedReader:
+"""
+ Open a data stream of the data that is being processed.
+
+ :param stream: data stream of trace to open. defaults to raw. other examples are html, text, etc.
+ :param offset: byte offset to start the stream on
+ :param size: the number of bytes to make available
+ :param buffer_size: buffer size for reading data
+ :return: a file-like object to read bytes from the named stream
+ """
+
+
+
+
+
+[docs]
+classMetaExtractionTrace(Trace):
+"""
+ MetaExtractionTraces contain only metadata.
+
+ This class represents traces during the extraction of an extraction plugin without a data stream.
+ """
+
+
+[docs]
+ @abstractmethod
+ defupdate(self,key_or_updates:Optional[Union[Mapping,str]]=None,value:Optional[Any]=None,
+ data:Optional[Mapping[str,bytes]]=None)->None:
+"""
+ Update or add metadata properties for this `.ExtractionTrace`.
+
+ :param key_or_updates: either a `str` (the metadata property to be
+ updated) or a mapping supplying both keys and values to be updated
+ :param value: the value to update metadata property *key* to (used
+ only when *key_or_updates* is a `str`, an exception will be thrown
+ if *key_or_updates* is a mapping)
+ :param data: a `dict` mapping data type / stream name to bytes to be
+ added to the trace
+ """
+
+
+
+[docs]
+ @abstractmethod
+ defadd_tracelet(self,
+ tracelet:Union[Tracelet,str],
+ value:Optional[Mapping[str,Any]]=None)->None:
+"""
+ Add a `.Tracelet` to this `.MetaExtractionTrace`.
+
+ :param tracelet: the Tracelet or tracelet type to add
+ :param value: the tracelet properties to add (only applicable when *tracelet* is a tracelet type)
+ """
+
+
+
+[docs]
+ @abstractmethod
+ defadd_transformation(self,data_type:str,transformation:Transformation)->None:
+"""
+ Update or add transformations for this `.ExtractionTraceBuilder`.
+
+ :param data_type: data type of the Transformation
+ :param transformation: the Transformation to add
+ """
+
+
+
+[docs]
+ @abstractmethod
+ defchild_builder(self,name:Optional[str]=None)->ExtractionTraceBuilder:
+"""
+ Create a `.TraceBuilder` to build a trace to be saved as a child of this `.Trace`.
+
+ A new trace will only be added to the index once explicitly saved (e.g.
+ through `.TraceBuilder.build`).
+
+ .. note::
+ Traces should be created and built in depth first order,
+ parent before child (pre-order).
+
+ :param name: the name for the trace being built
+ :return: a `.TraceBuilder` set up to create a child trace of this `.MetaExtractionTrace`
+ """
+
+
+
+
+
+[docs]
+classExtractionTrace(MetaExtractionTrace):
+"""Trace offered to be processed."""
+
+
+[docs]
+ @abstractmethod
+ defopen(self,data_type:Optional[str]=None,offset:int=0,size:Optional[int]=None,
+ mode:Literal['rb','wb','w','wt']='rb',encoding='utf-8',buffer_size:Optional[int]=None) \
+ ->Union[BufferedReader,BufferedWriter,TextIOBase]:
+"""
+ Open a data stream to read or write data from or to the `.ExtractionTrace`.
+
+ :param data_type: the data type of the datastream, 'raw' by default
+ :param offset: byte offset to start the stream on when reading
+ :param size: the number of bytes to make available when reading
+ :param mode: 'rb' for reading, 'wb' for writing
+ :param encoding: encoding for writing text, used to convert `str` values to bytes, \
+ only valid for modes 'w' and 'wt'
+ :param buffer_size: buffer size for reading (cache read back/ahead) or writing (cache for flush) data
+ :return: a file-like object to read or write bytes from the named stream
+ """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/plugin_info.html b/0.9.16/_modules/hansken_extraction_plugin/api/plugin_info.html
new file mode 100644
index 0000000..be5e3fe
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/plugin_info.html
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.plugin_info — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.plugin_info
+"""This module contains all definitions to describe metadata of a plugin, a.k.a. PluginInfo."""
+fromdataclassesimportdataclass,field
+fromenumimportEnum
+fromtypingimportDict,List,Optional
+
+
+
+[docs]
+@dataclass(frozen=True)
+classAuthor:
+"""
+ The author of an Extraction Plugin.
+
+ This information can be retrieved by an end-user from Hansken.
+ """
+
+ name:str
+ email:str
+ organisation:str
+
+
+
+
+[docs]
+classMaturityLevel(Enum):
+"""This class represents the maturity level of an extraction plugin."""
+
+ PROOF_OF_CONCEPT=0
+ READY_FOR_TEST=1
+ PRODUCTION_READY=2
+
+
+
+
+[docs]
+@dataclass(frozen=True)
+classPluginId:
+"""Identifier of a plugin, consisting of domain, category and name. Needs to be unique among all tools/plugins."""
+
+ domain:str
+ category:str
+ name:str
+
+ def__str__(self):
+ returnf'{self.domain}/{self.category}/{self.name}'.lower()
+
+
+
+
+[docs]
+@dataclass(frozen=True)
+classPluginResources:
+"""PluginResources contains information about how many resources will be used for a plugin."""
+
+ maximum_cpu:Optional[float]=None
+"""
+ CPU resources are measured in cpu units. One cpu is equivalent to 1 vCPU/Core for cloud providers and 1 hyperthread
+ on bare-metal Intel processors. Also, fractional requests are allowed. A plugin that asks 0.5 CPU uses half as
+ much CPU as one that asks for 1 CPU.
+ """
+ maximum_memory:Optional[int]=None
+"""Max usable memory for a plugin, measured in megabytes."""
+ maximum_workers:Optional[int]=None
+"""The number of concurrent workers(i.e. traces that can be processed)."""
+
+ def__post_init__(self):
+ ifself.maximum_cpuisnotNoneandself.maximum_cpu<0:
+ raiseValueError(f'maximum_cpu cannot be < 0: {self.maximum_cpu}')
+ ifself.maximum_memoryisnotNoneandself.maximum_memory<0:
+ raiseValueError(f'maximum_memory cannot be < 0: {self.maximum_memory}')
+ ifself.maximum_workersisnotNoneandself.maximum_workers<0:
+ raiseValueError(f'maximum_workers cannot be < 0: {self.maximum_workers}')
+
+
+
+
+[docs]
+@dataclass(frozen=True)
+classTransformerLabel:
+"""
+ TransformerLabel contains information about a transformer method that a plugin provides.
+
+ It is mainly used for storing the properties (name, arguments, return type) of a transformer in PluginInfo objects.
+ Unlike the Transformer class it does not contain the actual function reference to the transformer itself.
+ """
+
+"""The method name of the transformer. For example: my_method"""
+ method_name:str
+
+"""The parameters of the function where the key is the parameter name and the value is the type of the parameter."""
+ parameters:Dict[str,str]
+
+"""The return type of the parameter. See api.Transformer class for supported types."""
+ return_type:str
+
+
+
+
+[docs]
+@dataclass
+classPluginInfo:
+"""
+ This information is used by Hansken to identify and run the plugin.
+
+ Note that the build_plugin.py build script is used to build a plugin docker image with PluginInfo docker labels.
+ """
+
+ id:PluginId#: a plugin's unique identifier, see PluginId
+ version:str#: version of the plugin
+ description:str#: short description of the functionality of the plugin
+ author:Author#: the plugin's author, see Author
+ maturity:MaturityLevel#: maturity level, see MaturityLevel
+ matcher:str#: this matcher selects the traces offered to the plugin
+ webpage_url:str#: plugin url
+ license:Optional[str]=None#: license of this plugin
+ deferred_iterations:int=1#: number of deferred iterations (1 to 20), nly for deferred plugins (optional)
+ resources:Optional[PluginResources]=None#: resources to be reserved for a plugin (optional)
+ bulk_mode:Optional[bool]=None#: this plugin should be run as a plugin with bulk mode enabled (optional)
+
+"""Populated dynamically in pack.plugin_info by collecting all @transformer methods. Do not assign manually."""
+ transformers:List[TransformerLabel]=field(default_factory=list)
+
+ def__post_init__(self):
+ ifnot1<=self.deferred_iterations<=20:
+ raiseValueError(f'Invalid value for deferred_iterations: {self.deferred_iterations}. '
+ f'Valid values are 1 =< 20.')
+
+
Source code for hansken_extraction_plugin.api.search_result
+"""This module contains a representation of a search result."""
+fromabcimportABC,abstractmethod
+fromitertoolsimportislice
+fromtypingimportIterable,List,Optional
+
+fromhansken_extraction_plugin.api.extraction_traceimportSearchTrace
+
+
+
+[docs]
+classSearchResult(ABC,Iterable):
+"""
+ Class representing a stream of traces, returned when performing a search request.
+
+ This result can only be iterated once. Results can be retrieved in three ways:
+
+ Treating the result as an iterable:
+
+ .. code-block:: python
+
+ for trace in result:
+ print(trace.name)
+
+ Calling `.take` to process one or more batches of traces:
+
+ .. code-block:: python
+
+ first_100 = result.take(100)
+ process_batch(first_100)
+
+ Calling `.takeone` to get a single trace:
+
+ .. code-block:: python
+
+ first = result.takeone()
+ second = result.takeone()
+
+ print(first.name, second.name)
+
+ """
+
+
+[docs]
+ @abstractmethod
+ deftotal_results(self)->int:
+"""
+ Return the total number of hits.
+
+ :return: Total number of hits
+ """
+ pass
+
+
+
+[docs]
+ deftakeone(self)->Optional[SearchTrace]:
+"""
+ Return a single trace, if this stream is not exhausted.
+
+ :return: A searchtrace, or None if no trace is available
+ """
+ returnnext(self.__iter__(),None)
+
+
+
+[docs]
+ deftake(self,num:int)->List[SearchTrace]:
+"""
+ Return a list containing at most num number of traces, or less if they are not available.
+
+ :param num: Number of traces to take
+ :return: List containing zero or more traces
+ """
+ returnlist(islice(self.__iter__(),num))
+
+
+
+[docs]
+ defclose(self):
+"""
+ Close this SearchResult if no more traces are to be retrieved.
+
+ Required to keep compatibility with hansken.py.
+ """
+ pass
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/search_sort_option.html b/0.9.16/_modules/hansken_extraction_plugin/api/search_sort_option.html
new file mode 100644
index 0000000..089d124
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/search_sort_option.html
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.search_sort_option — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.search_sort_option
+"""This module defines the sort options for a search within Hansken."""
+fromenumimportEnum
+
+
+
+[docs]
+classDirection(Enum):
+"""
+ Enumeration for sorting directions.
+
+ Attributes:
+ ASCENDING: Sort in ascending order.
+ DESCENDING: Sort in descending order.
+ """
+
+ ASCENDING='ASCENDING'
+ DESCENDING='DESCENDING'
+
+
+
+
+[docs]
+classSearchSortOption:
+"""
+ Represents a search sort option consisting of a field and a direction.
+
+ Attributes:
+ _field (str): The field to sort by.
+ _direction (Direction): The direction to sort in (ascending or descending).
+ """
+
+ def__init__(self,field:str='uid',direction:Direction=Direction.ASCENDING):
+"""
+ Initialize a new SearchSortOption.
+
+ Args:
+ field (str): The name of the field to sort on.
+ direction (Direction): The direction to sort (ascending or descending).
+ """
+ self._field=field
+ self._direction=direction
+
+
+[docs]
+ deffield(self):
+"""
+ Get the field to sort on.
+
+ Returns:
+ str: Returns the current field.
+ """
+ returnself._field
+
+
+
+[docs]
+ defdirection(self):
+"""
+ Get the sorting direction.
+
+ Returns:
+ Direction: Returns the current direction.
+ """
+ returnself._direction
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/trace_searcher.html b/0.9.16/_modules/hansken_extraction_plugin/api/trace_searcher.html
new file mode 100644
index 0000000..d92527a
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/trace_searcher.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.trace_searcher — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.trace_searcher
+"""This module contains the definition of a trace searcher."""
+fromabcimportabstractmethod
+fromenumimportEnum
+fromtypingimportOptional,Union
+
+fromhansken_extraction_plugin.api.search_resultimportSearchResult
+fromhansken_extraction_plugin.api.search_sort_optionimportSearchSortOption
+
+
+
+[docs]
+classSearchScope(str,Enum):
+"""Scope to describe the search context for TraceSearcher.search calls."""
+
+ image='image'
+ project='project'
+
+
+
+
+[docs]
+classTraceSearcher:
+"""This class can be used to search for traces, using the search method."""
+
+
+[docs]
+ @abstractmethod
+ defsearch(self,query:str,count:Optional[int]=None,scope:Union[str,SearchScope]=SearchScope.image,
+ start:int=0,sort:list[SearchSortOption]=[SearchSortOption()])->SearchResult:
+"""
+ Search for indexed traces in Hansken using provided query returning at most count results.
+
+ :param query: HQL-query used for searching
+ :param start: Starting index of traces to return
+ :param count: Maximum number of traces to return
+ :param scope: Select search scope: 'image' to search only search for other traces within the image of the trace
+ that is being processed, or 'project' to search in the scope of the full project (either Scope-
+ enum value can be used, or the str-values directly).
+ :param sort: The fields and directions to sort on
+ :return: SearchResult containing found traces
+ """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/tracelet.html b/0.9.16/_modules/hansken_extraction_plugin/api/tracelet.html
new file mode 100644
index 0000000..ae367e5
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/tracelet.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.tracelet — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.tracelet
+"""This module contains the definition of a Tracelet."""
+fromtypingimportAny,Mapping
+
+
+
+[docs]
+classTracelet:
+"""
+ A tracelet contains the values of a single fvt (Few Valued Type).
+
+ A few valued type is a trace property type that is a collection of tracelets. A trace can contain multiple few
+ valued types containing one or more tracelets. For example, the `trace.identity`` type may look like this::
+
+ {emailAddress: 'interesting@notreally.com'},
+ {firstName: 'piet'},
+ {emailAddress: 'anotheremail@notreally.com'},
+
+ The trace.identity few valued types contains three different tracelets.
+ """
+
+ def__init__(self,name:str,value:Mapping[str,Any]):
+"""
+ Initialize a tracelet.
+
+ :param name: name or type of the tracelet. In the example this would be ``identity``.
+ :param value: Mapping of keys to properties of this tracelet. In the example this could be
+ ``{emailAddress: 'anotheremail@notreally.com'}``.
+ """
+ self.name=name
+ self.value=value
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/transformation.html b/0.9.16/_modules/hansken_extraction_plugin/api/transformation.html
new file mode 100644
index 0000000..b2fbdf6
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/transformation.html
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.transformation — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.transformation
+"""This module contains the definition of a Transformation."""
+fromabcimportABC
+fromdataclassesimportdataclass
+fromtypingimportList
+
+
+
+[docs]
+classTransformation(ABC):
+"""A super class for data transformations. Currently only :class:RangedTransformation is supported."""
+
+ pass
+
+
+
+
+[docs]
+@dataclass(frozen=True)
+classRange:
+"""A Range describes a range of bytes with a offset and length."""
+
+ offset:int#: the starting point of the data
+ length:int#: the size of the data
+
+
+
+
+[docs]
+classRangedTransformation(Transformation):
+"""A :class:RangedTransformation describes a data transformation consisting of a list of :class:Range."""
+
+ def__init__(self,ranges:List[Range]):
+""":type ranges: list of :class:Range."""
+ self.ranges=ranges
+
+
+[docs]
+ @staticmethod
+ defbuilder():
+""":return a Builder."""
+ returnRangedTransformation.Builder()
+
+
+
+[docs]
+ classBuilder:
+"""Helper class that implements a transformation builder."""
+
+ def__init__(self)->None:
+"""Initialize a Builder."""
+ self._ranges:List[Range]=[]
+
+
+[docs]
+ defadd_range(self,offset:int,length:int)->'RangedTransformation.Builder':
+"""
+ Add a range to a ranged transformation by providing the range's offset and length.
+
+ :param offset the offset of the data transformation
+ :param length the length of the data transformation
+ :return: this `.RangedTransformation.Builder`
+ """
+ ifoffsetisNone:
+ raiseValueError('offset is required')
+ iflengthisNone:
+ raiseValueError('length is required')
+ self._ranges.append(Range(offset=offset,length=length))
+ returnself
+
+
+
+[docs]
+ defbuild(self)->'RangedTransformation':
+"""
+ Return a RangedTransformation.
+
+ :return: a :class:RangedTransformation
+ """
+ returnRangedTransformation(ranges=self._ranges)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/hansken_extraction_plugin/api/transformer.html b/0.9.16/_modules/hansken_extraction_plugin/api/transformer.html
new file mode 100644
index 0000000..eb934f6
--- /dev/null
+++ b/0.9.16/_modules/hansken_extraction_plugin/api/transformer.html
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.transformer — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source code for hansken_extraction_plugin.api.transformer
+"""
+This module contains the Transformer class that holds the function reference of the transformer.
+
+Instances of this class are constructed by BaseExtractionPlugin when retrieving transformers dynamically.
+It also validates whether the method to which @transformer is applied adheres to the requirements of a transformer.
+"""
+
+fromdatetimeimportdatetime
+importinspect
+fromtypingimportMapping,Sequence
+
+fromhansken.utilimportGeographicLocation,Vector
+
+fromhansken_extraction_plugin.api.plugin_infoimportTransformerLabel
+fromhansken_extraction_plugin.utility.type_conversionimportget_type_name
+
+
+
+[docs]
+classTransformer:
+"""
+ A transformer is an exposed method of a plugin that can be executed remotely outside extraction-time.
+
+ This allows for on-demand analysis during an investigation.
+ """
+
+"""
+ This dictionary holds the supported types that transformer methods can as arguments and return types.
+
+ The keys are the supported Python types and the values the generic type names as in the Hansken trace model.
+ Note that these types should also be defined in _primitive_matchers in runtime.pack for successful serialization.
+ In order to not create circular dependencies and separate the runtime module and the api module this is defined
+ separately here.
+ """
+ supported_primitives={bytes:'binary',
+ bool:'boolean',
+ int:'integer',
+ float:'real',
+ str:'string',
+ datetime:'date',
+ GeographicLocation:'latLong',
+ Vector:'vector',
+ Sequence:'list',
+ Mapping:'map'}
+
+ def__init__(self,function):
+"""Create a transformer and validate whether the passed function meets the requirements."""
+ self.function=function
+
+ # Retrieve the signature so that we can validate whether it complies to the transformer requirements.
+ signature=inspect.signature(function)
+
+ # Validate that @transformer was applied to a function/method and not any other type of object (i.e. a class)
+ ifnotfunction.__class__.__name__=='function':
+ raiseException('@transformer was applied to something other than a function/method. '
+ '@transformer may only be applied to methods of classes derived from BaseExtractionPlugin.')
+
+ # Validate that @transformer was applied to a method instead of a function.
+ if'.'notinfunction.__qualname__:
+ raiseException(
+ '@transformer was applied to a function instead of a method of a class derived from '
+ 'BaseExtractionPlugin. '
+ '@transformer may only be applied to methods of classes derived from BaseExtractionPlugin.')
+
+ # Extract the method name for ease of use.
+ self.method_name=function.__qualname__.split('.')[1]
+
+ # Validate if this function is not a static method.
+ # Note: This is not entirely foolproof since the self parameter may officially also be named differently or as
+ # a non-first argument.
+ if'self'notinsignature.parameters:
+ raiseException('@transformer may not be applied to static methods.')
+
+ # Validate all the parameters and store them.
+ self.parameters={}
+ forparameterinsignature.parameters.values():
+
+ # Other than validating the self property we don't include it in the parameters field because we do not
+ # want to expose it externally.
+ # Note: This check is not fool-proof since parameters called self can be defined as non-first parameters.
+ ifparameter.name=='self':
+ ifparameter.annotationisnotinspect.Parameter.empty:
+ raiseException('@transformer methods should have a parameter self without a type annotation.')
+ else:
+ continue
+
+ # Validate if annotations are present on each parameter.
+ ifparameter.annotationisinspect.Parameter.empty:
+ raiseException('The parameters of @transformer methods must have type hints.')
+
+ # Validate that parameters do not have default values.
+ ifparameter.defaultisnotinspect.Parameter.empty:
+ raiseException('@transformer methods are currently not allowed to have parameters with a '
+ 'default value.')
+
+ # Do not allow variable arguments or positional only arguments.
+ ifparameter.kindin[parameter.POSITIONAL_ONLY,parameter.VAR_POSITIONAL,parameter.VAR_KEYWORD]:
+ raiseException('@transformer methods are currently not allowed to have positional only parameters or '
+ 'variable parameters (like *args and **kwargs).')
+
+ # Validate if a parameter is one of the supported serializable types.
+ ifparameter.annotationnotinTransformer.supported_primitives.keys():
+ raiseException(f'The parameters of @transformer methods should be one of '
+ f'{[get_type_name(x)forxinTransformer.supported_primitives]}')
+
+ self.parameters[parameter.name]=parameter.annotation
+
+ # Validate if the return annotation is present and one of the supported serializable types.
+ ifsignature.return_annotationnotinTransformer.supported_primitives.keys():
+ raiseException(f'The return type of @transformer methods should be one of '
+ f'{[get_type_name(x)forxinTransformer.supported_primitives.keys()]}')
+
+ self.return_type=signature.return_annotation
+
+
+[docs]
+ defgenerate_label(self)->TransformerLabel:
+"""
+ Generate a TransformerLabel given the transformer method. TransformerLabels are used in PluginInfo objects.
+
+ Unlike Transformers TransformerLabels can be serialized and sent to a client that wishes to call a transformer.
+ The specific Python types are converted to the generic types that are used in the Hansken trace model.
+ """
+ # Convert the parameters to a generic Hansken parameter names.
+ # No checks are needed here because they are already performed upon initialization of a Transformer.
+ parameters={name:self.supported_primitives[param_type]forname,param_typeinself.parameters.items()}
+ return_type=self.supported_primitives[self.return_type]
+ returnTransformerLabel(method_name=self.method_name,
+ parameters=parameters,
+ return_type=return_type)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_modules/index.html b/0.9.16/_modules/index.html
new file mode 100644
index 0000000..abd079e
--- /dev/null
+++ b/0.9.16/_modules/index.html
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+ Overview: module code — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/_sources/changes.rst.txt b/0.9.16/_sources/changes.rst.txt
new file mode 100644
index 0000000..3277514
--- /dev/null
+++ b/0.9.16/_sources/changes.rst.txt
@@ -0,0 +1,15 @@
+.. _changelog:
+
+Changelog
+=========
+
+The following page lists all (technical) changes in the extraction plugin SDK.
+
+Programming language specific API changes are described in more detail on API changelog pages.
+These pages list new API functionalities, and describe how to update your plugins when API changes are in order.
+For the API changelog pages see:
+
+* :doc:`dev/java/api_changelog`
+* :doc:`dev/python/api_changelog`
+
+.. mdinclude:: ../CHANGES.md
diff --git a/0.9.16/_sources/contact.md.txt b/0.9.16/_sources/contact.md.txt
new file mode 100644
index 0000000..073d2ad
--- /dev/null
+++ b/0.9.16/_sources/contact.md.txt
@@ -0,0 +1,16 @@
+# Contact
+
+Please get in touch with us:
+
+* if you have questions about the SDK,
+* have found a bug,
+* have a feature request,
+* see other opportunity to improve,
+* want to contribute,
+* ...
+
+Chat with us on [Discord](https://www.discord.com). You can find members of the extraction plugin SDK development team
+in the Hansken Community server, in the `extraction-plugins` channel. This is a Hansken community-private server. If you
+don't have access to the Hansken Community server, please contact your Hansken business owner. He or she can invite you
+to the Hansken Community server. If you don't know who to contact, feel free to fill in
+the [contact form](https://www.hansken.nl/contact) for further questions/contact.
diff --git a/0.9.16/_sources/dev/concepts.rst.txt b/0.9.16/_sources/dev/concepts.rst.txt
new file mode 100644
index 0000000..7348949
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts.rst.txt
@@ -0,0 +1,21 @@
+General concepts
+================
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Contents:
+
+ concepts/extraction_plugins
+ concepts/anatomy_of_a_plugin
+ concepts/plugin_types
+ concepts/plugin_naming_convention
+ concepts/traces
+ concepts/hql_lite
+ concepts/data_transformations
+
+ concepts/test_framework
+ concepts/all_in_one_debugging
+
+ concepts/isolation
+ concepts/kubernetes_autoscaling
+
diff --git a/0.9.16/_sources/dev/concepts/all_in_one_debugging.md.txt b/0.9.16/_sources/dev/concepts/all_in_one_debugging.md.txt
new file mode 100644
index 0000000..18df95f
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/all_in_one_debugging.md.txt
@@ -0,0 +1,33 @@
+# Debugging locally with Hansken All in One (AIO)
+
+.. note:: this feature is available from Hansken 46.4.0
+
+.. warning:: `DeferredExtractionPlugin` are currently NOT supported
+
+It is also possible to debug an Extraction Plugin with a locally running Hansken AIO. This requires a few steps:
+
+1. Start your plugin in your IDE (default port 8999)
+ 
+2. Set some breakpoint(s)
+ 
+3. Prepare an extraction in the AIO **with advanced** options
+ 
+4. Enable the `DebugExtractionPluginTool` for this extraction
+ 
+5. Start extraction, and happy debugging!
+
+## Tips/notes
+
+- You can only debug 1 plugin at a time
+ - .. note:: If you are debugging `MyPlugin`, and it is also visible in the tools list, then you need to **disable** it, and **only enable** the `DebugExtractionPluginTool`
+- Test your plugin with a small image. Otherwise, it might take long before you reach your breakpoint.
+- Be careful when using this debugger with `APPEND` extractions:
+ - Similar to other tools/plugins, the `DebugExtractionPluginTool` will only run once per trace
+ - So if you need to re-run your debug session, then we advise you to re-extract `(INDEX)` your project instead
+- Hansken runs multiple instances of every Tool, so the same breakpoint can be hit multiple times concurrently by
+ different instances
+ - limiting the number of plugin threads/workers can minimize this
+ - see [Specifying system resources (Python)](../python/snippets.md#Specifying system resources)
+- Only restart your plugin before starting an extraction. Restarting your plugin **during** an extraction can
+ produce
+ **undefined behaviour**.
diff --git a/0.9.16/_sources/dev/concepts/anatomy_of_a_plugin.md.txt b/0.9.16/_sources/dev/concepts/anatomy_of_a_plugin.md.txt
new file mode 100644
index 0000000..c8c6ff4
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/anatomy_of_a_plugin.md.txt
@@ -0,0 +1,72 @@
+# Anatomy of a plugin
+
+This page describes the general anatomy of a plugin, and its (simplified) execution in Hansken.
+
+## The plugin itself
+
+Each plugin must implement two methods:
+
+* `pluginInfo()`: This method returns information about the plugin.
+* `process()`: This method performs the extraction task of the plugin.
+
+Let's dive a bit deeper into these methods in the next sections!
+
+### The method `pluginInfo()`
+
+The `pluginInfo()` method returns a `PluginInfo` object. Hansken needs this object to be able to know the capabilities
+of the plugin, and to show the plugin in the list of tools. The most important fields that must be set on `PluginInfo`
+are the following:
+
+| `id` | The identifier of the plugin. This will be used as a unique name for the plugin Hansken. |
+| `description` | A description of the plugin that is shown in Hansken. |
+| `author` | The author of the plugin that is shown in Hansken. |
+| `license` | The type of license of the plugin that is shown in Hansken. |
+| `matcher` | This matcher is used by Hansken to determine which Traces are sent to the Plugin during extraction. |
+
+### The method `process()`
+
+During extraction, Hansken calls the `process()` method for every matching trace. The `matcher` attribute of
+the `PluginInfo` is very important as it determines which traces will be sent to the `process` method.
+
+Although the plugin developer is free to program whatever seems useful, the following tasks are typically performed
+within the `process()` method:
+
+* Creating child-traces
+* Reading trace properties
+* Adding trace properties
+* Reading the data that the Trace represents
+* Writing data on a Trace
+
+Depending on the type of plugin that is implemented, different functionality is available in the `process()` method.
+See [Plugin Types](plugin_types.md) for more details.
+
+## The execution in Hansken
+
+This describes the process of running a plugin from the perspective of Hansken. The perspective of the user is described
+in [Hansken Extraction Plugins](extraction_plugins.md).
+
+### Plugin discovery
+
+Hansken manages a list of tools that can be used in extractions. The available plugins must be added to this list so
+that the user can select them. To accomplish this, Hansken scans the Docker registry for docker images that are plugins.
+Each image is started up, and a call is done to its `pluginInfo()` method. If the call resulted in a valid `PluginInfo`
+object, the Extraction Plugin is added to the list of tools visible to users. After the `PluginInfo` is retrieved, the
+docker image is shutdown again.
+
+### Starting an extraction
+
+Hansken checks if any plugins are selected by the user that started the extraction. For each selected plugin, at least
+one docker image will be started. See [Kubernetes autoscaling](kubernetes_autoscaling.md) for more details on expanding
+the number of instances for each plugin.
+
+### Extracting
+
+During an extraction, Hansken will iteratively loop through all selected tools, including Extraction Plugins. For each
+trace that matches on a tool, Hansken will call its `process` method. For Extraction Plugins, this means that
+the `process` method is called via the gRPC protocol. The trace to be processed is sent over gRPC to the plugin, and any
+other communication between Hansken and the Extraction Plugin (like created properties and child traces, search requests
+and written data) are done using gRPC.
+
+### Finishing an extraction
+
+At the end of an extraction, Hansken will stop all associated plugins.
diff --git a/0.9.16/_sources/dev/concepts/data_transformations.md.txt b/0.9.16/_sources/dev/concepts/data_transformations.md.txt
new file mode 100644
index 0000000..116c0a5
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/data_transformations.md.txt
@@ -0,0 +1,24 @@
+# Data Transformations
+
+Extraction Plugins can create new :ref:`data-streams ` on a Trace through data transformations. Data
+transformations describe how data can be obtained from a source. Data transformations are preferred over storing blobs
+because they take less space. This is because they only describe the data instead of specifying the actual data.
+
+The following figure shows how Hansken visualizes data transformations:
+
+Note that transformations can be applied on transformations. The SDK only supports range transformations at the moment,
+while this image also shows some transformations that are currently available in Hansken but not in the SDK.
+
+An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in
+the archive file. Each child trace will have a data stream that is a transformation that marks the start and length of
+the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of
+space is saved.
+
+Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data
+transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in a
+bytearray.
+
+## See also
+
+* [Data Transformations in Java](../java/snippets.md#Data Transformations)
+* [Data Transformations in Python](../python/snippets.md#Data Transformations)
diff --git a/0.9.16/_sources/dev/concepts/extraction_plugins.md.txt b/0.9.16/_sources/dev/concepts/extraction_plugins.md.txt
new file mode 100644
index 0000000..ced3be4
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/extraction_plugins.md.txt
@@ -0,0 +1,59 @@
+# Hansken Extraction Plugins
+
+Hansken Extraction Plugins enable Hansken users to add their own extraction tools to Hansken.
+
+To use an Extraction Plugin in Hansken, the following steps have to be done:
+
+1. Build the plugin
+2. Upload the plugin to Hansken
+3. Refresh the Hansken tools list
+4. Start an extraction with the plugin enabled
+
+## Building a plugin
+
+Hansken Extraction Plugins can be built in Java or Python by implementing an interface. Which interface you choose
+depends on the type of plugin you choose to make, see [Plugin Types](plugin_types.md). For more information on coding
+your own plugin, see the [Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples).
+
+The plugin can then be tested using the [Test Framework](test_framework.md). This way you make sure everything works as
+expected before taking further steps.
+
+## Package the plugin
+
+To upload a plugin into Hansken, a docker image for this plugin must be uploaded to the docker registry. First, the
+plugin container image must be packaged.
+A plugin is packaged into an OCI image (also known as Docker image).
+
+* [Package a docker image for a Python plugin](../python/packaging.md)
+* [Package a docker image for a Java plugin](../java/packaging.md)
+
+.. _upload_plugin:
+
+## Upload the plugin to Hansken
+
+Hansken finds the plugins by scanning a docker registry. It will try to load all docker images with a certain prefix as
+Extraction Plugins. The settings for this are defined in Hansken properties:
+
+* `registry.extraction.plugins.registry.uri` defines the registry
+* `registry.extraction.plugins.registry.prefix` defines the prefix plugins must have
+
+When the image is packaged locally, it needs to be pushed (uploaded) to the docker registry. These commands provide an
+outline of how to do this:
+
+* `docker login ` (make sure you are logged in to the registry)
+* `docker push ` (push the plugin docker image to the registry)
+
+.. note:: For more information about uploading plugins and running them in Hansken, see the 'Using extraction plugins in
+ Hansken' chapter of the *Hansken User Guide*.
+
+## Refresh the Hansken tools list
+
+Hansken checks which plugins are available at startup. The list of available plugins can also be refreshed by calling
+the following endpoint: `/gatekeeper/tools?refresh=true`
+
+This can be invoked via an internet browser.
+
+## Start an extraction with the plugin enabled
+
+If everything went well, the list of available tools in Hansken should now feature your plugin. To run the plugin in an
+extraction, be sure to select its checkbox in the extraction tools dialog.
diff --git a/0.9.16/_sources/dev/concepts/hql_lite.md.txt b/0.9.16/_sources/dev/concepts/hql_lite.md.txt
new file mode 100644
index 0000000..877576e
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/hql_lite.md.txt
@@ -0,0 +1,375 @@
+# HQL-Lite
+
+## Overview
+
+HQL-Lite is a query language derived from Hanskens full HQL human. HQL stands for Hansken Query Language and can be
+used to search or match traces. Since not all elements of full HQL can be used in the context of an extraction,
+extraction plugins use HQL-Lite, a lightweight version of HQL. This document describes the usage of HQL-Lite in the
+context of extraction plugins.
+
+.. _howdoeshanskenwork:
+
+## How does Hansken work?
+
+- Let's say we have a Hansken image `hansken_image1` with 10 pdf files, and 5 jpegs.
+- And our Hansken contains 2 tools:
+ - PdfPlugin
+ - JpegTool
+
+.. note:: All plugins are Hansken tools, but not all Hansken tools are plugins. Some tools are included in Hansken core.
+
+Let's look at a (simplified) pseudocode example of the inner workings of Hansken:
+
+```python
+ for each trace in new_traces {
+ for each datastream in trace {
+ for each tool in hansken_tools {
+ if tool.can_this_tool_process_the_provided_trace(trace, datastream) {
+ tool.process_the_trace(trace, datastream)
+ }
+ }
+ }
+ }
+```
+
+So in this example we know the following:
+
+- `new_traces` has
+ - 10 pdf files
+ - 5 jpeg files
+- `hansken_tools` contains:
+ - PdfPlugin
+ - JpegTool
+
+So the question here is, how do we prevent that traces are not processed by incompatible tools?
+
+The answer is the `tool.can_this_tool_process_the_provided_trace()` part of the pseudocode.
+
+### What does `can_this_tool_process_the_provided_trace()` do?
+
+Hansken actually contains many more tools/plugins than these 2, and instead of 15 files/traces, we usually deal with
+millions.
+
+.. note:: If each trace has 1 extra second of overhead, 1 million traces would take 11.5 days of extra CPU time
+
+#### Matchers to the rescue
+
+To reduce the unnecessary overhead of processing all traces (even the ones the tool cannot actually process), Hansken
+implements the concept of a `matcher` for each tool. This _matcher_ basically checks the _trace_ for _"matching
+conditions"_, that would allow the tool to process it.
+
+Sometimes these _matching conditions_ can be as simple as a specific `filename` or `extension`, but are often more
+elaborate in the sense that they check multiple factors that require some intimate knowledge of Hansken.
+
+## What is HQL-Lite?
+
+HQL-Lite is a language based on HQL (Hansken Query Language) that allows plugin developers to write _matchers_ for
+Hansken Extraction Plugins. It could be said that HQL-Lite contains a subset of HQL features, plus some HQL-Lite unique
+features that are only interesting for _matchers_.
+
+.. note::
+ Please note that even though the HQL-Lite query is part of the plugin, it is compiled and stored in Hansken during
+ startup to achieve performance.
+
+### Why not just use HQL for plugins?
+
+HQL was designed to search for traces stored in the Elasticsearch database. As such, some of its features are tightly
+coupled to the Elasticsearch implementation, making it difficult to re-implement them for plugins.
+
+Also, even though HQL is more complex than the requirements for _matching_ in plugins, a couple of minor features that
+are absolutely necessary for _matching_ are not implemented in HQL, as they don't make much sense from a search point of
+view. This is because HQL was designed to be used with _finished extractions_ with all the traces stored in the
+database, while HQL-Lite was designed for _active extractions_.
+
+.. _hqllite syntax:
+
+### HQL-Lite syntax
+
+| Matcher | Syntax | remarks |
+|----------|------------------------|--------------------------------------------------------------------------------------------------------|
+| All | `""` | an empty string translates to match for __all__ traces |
+| And | `foo:1 AND bar:2` | the case-sensitive `AND` operator behaves like a logical AND of 2 conditions |
+| Not | `NOT foo` or `-foo` | the case-sensitive `NOT` or `-` negates the expression that follows |
+| Range | `foo>1` or `1<=foo<10` | a numbered-range check with a min or/and max range(s) |
+| Or | `foo:1 OR bar:2` | the case-sensitive `OR` operator behaves like a logical OR of 2 conditions |
+| Data | `$data.foo:1` | see `$data` section below |
+| DataType | `$data.type:raw` | this query matches against the type of the current datastream |
+| Types | `type:email` | this query checks if the trace contains a certain trace type as defined in the Hansken trace model |
+
+There are also a couple of general guidelines that apply to all matchers:
+
+- Equals/not equals:
+ - `:` or `=` : The most basic of left equals right statements. note that `=` is also valid.
+ - `!=` : The opposite of equals, not equals. Note that `!:` is __NOT__ supported.
+- Wildcards:
+ - `?` : Match against any single character. E.g. `foo:r?w` will match against `raw, row` but not against `rowing`.
+ - `*` : Match against any chars. E.g. `foo:r*` will match against `r, ra, raw, raaaaaw` but not against `aw`.
+- Exact match: By surrounding a value with quotes, we tell the parser that it is a single value. This is especially
+ helpful for values that might contain separators. E.g. `foo:'hello hql-lite'`.
+- CSV: Currently only the `type` query supports multiple values to check against. E.g. `type:email,chatMessage` will only
+ return `true` if both types exist for this trace.
+- `()` grouping: You can group statements by putting brackets around them. E.g. `foo:1 AND (bar:2 OR bla:3)` which
+ translates to `foo:1` plus one of the statements in the brackets.
+- Escaping `\"\\ .\t\r\n:=> this is the regex matcher, which is unsupported in HQL-lite
+ - `foo:c:\` -> should be `foo:c\:\\`, both the `colon` and the `slash` need to be escaped
+ - `foo:'c:\'` -> should be `foo:'c:\\'`, the `slash` still needs to be escaped
+ - .. note:: the backslash is the universal escape character, so it **always** needs to be escaped.
+
+#### $data matchers
+
+In Hansken, a trace can have multiple :ref:`datastreams `. The exact content of said datastreams is
+discussed elsewhere, but the basic idea is that a trace can have multiple representations. For example, a trace might
+have a `raw` datastream, but after we identify that the raw bytes contain a __text__ file, we might add a separate
+datastream `text`.
+
+.. note::
+ The `process()` method of each plugin is called for each datastream of each trace. This is explained
+ in :ref:`How does Hansken work? ` . Subsequently, you might have the same property for a
+ different datastream. For example: you might have a `data.raw.size` and a `data.text.size` property. The reason you
+ might have the same property multiple times, is because it could have a different meaning.
+
+For example:
+
+- data.raw.size: is the size in bytes
+- data.text.size: is the number of bytes in the text representation of the raw stream
+
+If we want to check if either of these properties is not empty by using a `$data` matcher, we do:
+
+```text
+ $data.size>0
+```
+
+##### When is it useful to use a $data matcher?
+
+For example, there is a simple plugin called `LetterCountPlugin`, that counts the letters in text based datastreams.
+
+So to match on these text based datastreams, we have 2 choices:
+
+- List all the possibilities
+ - Which is too tedious, and not very flexible when new types are supported
+- Match on a common property
+ - More compact, but sometimes difficult to find a common property
+
+In this case we might match on mimeType, which we know is `text/plain` or `text/x-log` for 2 of types we want to match:
+
+```text
+ $data.mimeType=text\\/*
+```
+
+This will match the following:
+
+- `data.text.mimeType=text\\/plain`
+- `data.text.mimeType=text\\/not\\ plain`
+- `data.pdf.mimeType=text\\/encoded`
+- `data.foo.mimeType=text\\/bar`
+
+But will __not__ match any of the following:
+
+- `data.text.mimeType=txt`
+- `data.text.mimeType=pdf`
+- `data.text.mime=text\\/plain`
+- `data.foo.bar=text\\/plain`
+
+## How to write a matcher?
+
+The functional requirements for writing a matcher can be summarized in the following:
+
+1. What does my plugin expect as input?
+2. How can I describe that input with the information Hansken provides?
+
+### PdfPlugin example
+
+Let's say we just finished writing a `PdfPlugin`. This is a simple plugin that checks if pdf files contain the
+word `the`.
+
+So let's go over our checklist:
+
+#### _What does my plugin expect as input?_
+
+PDF files.
+
+#### _How can I describe that input with the information Hansken provides?_
+
+Hansken consumes and produces :ref:`Traces `. To that effect, we can only match on trace properties that are
+available in Hansken.
+
+##### Match on extension
+
+The easiest way would be to only allow traces with the `.pdf` extension. Looking at the :ref:`Hansken trace model` (or a
+Hansken extraction), we can see that there's a property `file`
+which contains a property `extension`.
+
+So what would that look like in HQL-lite? Something like
+
+```text
+ file.extension=pdf
+```
+
+.. warning:: This of course **only** works if the file has the correct extension (note that matchers are case-sensitive).
+
+So what do we do, if we also want to match pdf files that are (un)intentionally misnamed?
+
+##### Match on mime-type
+
+Looking at Wikipedia, we see that `pdf` has a couple of mime-types. In return looking at our extraction and the
+trace-model, we see both at `data.raw.mimeType`, with a further explanation in the :ref:`Hansken trace model` that
+the `raw` portion of the property is the __data type__ of the datastream.
+
+If we don't know which datastream has the `mimeType` property beforehand, we could use the broad-scoped `$data.` matcher
+to look at every datastream.
+
+So our matcher becomes:
+
+```text
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+```
+
+##### Match on data size
+
+Some pdf files can be huge, meaning that parsing them will need a lot of resources. Could we add a data size check to
+the matcher? According to the :ref:`Hansken trace model` `data` has a property `size` (similar to `mimeType`) that we
+could use for this.
+
+.. note:: This is also a good way to check if a file is empty or not.
+
+Let's say our cutoff limit is 1 MB, meaning our matcher becomes:
+
+```text
+ 0 < $data.size < 1000000 AND
+ (
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+ )
+```
+
+##### Match if 'property is set'
+
+It is not uncommon to have some overlap between tools/plugins. For example:
+
+- PdfPlugin: a plugin that only supports pdf documents
+- DocumentPlugin: this plugin supports a lot of document types, including `pdf`.
+
+So how would we prevent our plugin from processing a trace that has already been processed by the `DocumentPlugin`?
+
+The easiest solution would be to check if a certain property has already been set. Meaning, that if both plugins set
+the `foo.bar` property, we check if said property has already been set.
+
+So we __only__ process the trace if `foo.bar` is __empty__, meaning our matcher becomes:
+
+```text
+ foo.bar!=* AND
+ 0 < $data.size < 1000000 AND
+ (
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+ )
+```
+
+##### Match on excluding a certain path
+
+It is also not uncommon to exclude certain paths from your plugin. These paths might contain invalid or encrypted files,
+for example.
+
+So let's say we want to exclude all files under in the `/tmp/virus` path. How do we go about it?
+
+Again, we check our extraction/:ref:`Hansken trace model`, and we see that `file.path` looks promising.
+
+So excluding `/tmp/virus` would look something like:
+
+```text
+ -file.path=/tmp/virus* AND
+ foo.bar!=* AND
+ 0 < $data.size < 1000000 AND
+ (
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+ )
+```
+
+.. _hql datastreams:
+
+##### Match on specific datastream type, an anti-pattern
+
+.. warning:: Matching on specific datastream types is an anti-pattern! It is not recommended to match on a datastream
+ type. Instead one should match on other datastream properties, such as `fileType`, `mimeType` or `mimeClass`.
+ The reason for this is explained in the paragraph below.
+
+Using a matcher that is too loose or too tight can yield unexpected results. Usually one will match on properties
+of a datastream like `fileType`, `mimeType` or `mimeClass` as these are reliable properties that have been added by
+Hansken tools. Matching on a specific datastream says nothing about the type of file. For example a PDF file may be
+available in a `raw` as well as in a `decrypted` datastream. By matching on the datastream type one may exclude traces
+that were not intended to be excluded.
+Contrarily, note that matching on a datastream type may include *more* traces than you expected as well. For example,
+someone may think "Plugin A puts data on the `plain` datastream, so I'll match on the `plain` datastream with Plugin B",
+forgetting that `plain` may be used by other tools as well. In other words, there may be traces with that datastream
+type that you did not know of, potentially crashing your plugin. See :ref:`datastreams` for more information.
+
+Now that you know why it is an anti-pattern, lets explain how it would be done (for those edge cases where it's needed):
+Lets say we want our `PdfPlugin` to __ONLY__ process `raw` datastreams.
+The best way to do this would be to match
+on `$data.type:raw`. Note that `$data.type` matches against the type of the current datastream, so in this case it
+matches only when the current datastream is of type `raw`.
+
+An __incorrect__ way to do it would be to replace `$data.` matcher(s) with `data.raw.`. This means the matcher
+will match whenever a trace has this datastream type, even if the current datastream type is different.
+Remember that the `process` method of an extraction plugin is always called once for each datastream on each trace.
+For example, lets say a trace has two datastreams, `raw` and `text`. The matcher would match for both the datastreams
+because the trace has a `raw` datastream (even though the current datastream type may be `text`). This results in the
+`process` method being called twice (for `raw` and for `text`), which may lead to other bugs if the developer doesn't
+know this. For example, the second time the plugin may be trying to overwrite data on a trace which is prohibited.
+
+So, using `$data.type`, our matcher would look like:
+
+```text
+ $data.type:raw AND
+ -file.path=/tmp/virus* AND
+ foo.bar!=* AND
+ 0 < $data.size < 1000000 AND
+ (
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+ )
+```
+
+## How precise should a matcher be?
+
+In practice, only you as the plugin dev can answer this question.
+
+Know that from the point of view of Hansken, we only care that the plugin:
+
+- __Should not crash__: If a matcher does not compile, then your plugin will not be available in Hansken. Tip: be sure
+ to test your plugin with the :ref:`test framework `.
+- __Should not be slow__: Matching is designed to be extremely fast, but of course, if you make it too complex it can
+ take longer than we want. In the example above, we calculated that 1 second extra for 1 million traces is 11 days of
+ extra CPU time. Unlike processing, matching is done for __every trace__, in every extraction iteration, so be careful!
+- __Should match on the bare minimum__: Don't go too far by matching 50 different criteria before allowing a trace to be
+ processed. Note that a lot of (if not all) of these criteria depend on properties set by other tools, and you don't
+ really have any control on how these tools work.
diff --git a/0.9.16/_sources/dev/concepts/isolation.md.txt b/0.9.16/_sources/dev/concepts/isolation.md.txt
new file mode 100644
index 0000000..8932753
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/isolation.md.txt
@@ -0,0 +1,28 @@
+# Plugin isolation
+
+Extraction plugins allows arbitrary code to be executed during a Hansken extraction. This code is executed inside the
+Hansken cluster. Extraction plugins are subjected
+to [Hanskens design principles](https://www.sciencedirect.com/science/article/pii/S1742287615000857?via%3Dihub)
+such as security, privacy and transparency. To ensure that plugins are compliant to these principles, each plugin will
+be executed in isolation. This page describes the isolation measures that are in place.
+
+## User isolation
+
+The following user restrictions are implied on the plugin:
+
+* The plugin can not run as `root`.
+* Instead, the plugin will run as user `1000`
+* and with group `2000`
+* and with fsgroup `3000`
+
+## System calls
+
+Plugins are only allowed to call a limited set of (Linux) system calls. This ensures that a plugin can be executed in a
+secure manner within the Hansken platform.
+
+Hansken uses Kubernetes to run extraction plugins. The Kubernetes `RuntimeDefault` secure computing mode (`seccomp`) is
+enabled to provide a sane default of available system calls.
+
+## Network isolation
+
+Plugins are not allowed to communicate over the network.
diff --git a/0.9.16/_sources/dev/concepts/kubernetes_autoscaling.md.txt b/0.9.16/_sources/dev/concepts/kubernetes_autoscaling.md.txt
new file mode 100644
index 0000000..6ffa7b4
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/kubernetes_autoscaling.md.txt
@@ -0,0 +1,27 @@
+# Kubernetes, Autoscaling, Resourcemanagement
+
+Extraction Plugins can be run in a Kubernetes cluster. This can be the same cluster Hansken run on, or another external
+cluster. For each plugin, a *pod* is created by Hansken. Each plugin will have 12 threads by default to process traces
+separately within one pod.
+
+## Autoscaling
+
+Hansken will create a Horizontal Pod Autoscaler (*HPA*) for each pod. HPA's manage the number of replica's of a pod
+based on metrics. The Extraction Plugins SDK provides two metrics to be set in the PluginInfo:
+
+* Observed CPU utilization
+* Observed memory usage
+
+For more info on how to set these metrics follow these links:
+
+* [Specifying system resources (Java)](../java/snippets.md#Specifying system resources)
+* [Specifying system resources (Python)](../python/snippets.md#Specifying system resources)
+
+If a pod reaches the CPU or memory usage provided with the PluginInfo, the HPA will increase the number of replicas for
+that plugin. Scaling down is done automatically. The maximum number of replicas per pod is specified within Hansken
+properties and can be adapted by an operator (needs restart).
+
+## Finding the right settings
+
+This depends on the kubernetes cluster settings, the nodes it runs on, and of course the plugin itself. Monitor the
+number of replica's and resource metrics while an extraction is running and adapt accordingly.
diff --git a/0.9.16/_sources/dev/concepts/plugin_naming_convention.md.txt b/0.9.16/_sources/dev/concepts/plugin_naming_convention.md.txt
new file mode 100644
index 0000000..cd0e9ad
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/plugin_naming_convention.md.txt
@@ -0,0 +1,44 @@
+# Plugin naming convention
+
+## Plugin identifier
+
+Each extraction plugin has a unique identifier. The identifier consists of three fields. These three fields combined
+form the plugin name.
+
+The three fields of a plugin identifier are: *domain*, *category*, and *name*. The fields are described in more detail
+below.
+
+**domain**
+The domain name of the organisation where the plugin is created. If an organisation has multiple domain names, the
+shortest name is preferred over the longer domain names. Examples: `nfi.nl`, `politie.nl`, `fiod.nl`, `hansken.org`.
+
+**category**
+A type of action that the plugin performs. The category is a free text field, but the following table gives some
+recommendations.
+
+| Category | Description |
+|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| ``extract`` | The plugin parses a clear data structure |
+| ``carve`` | The plugin parses data fragments to reassemble traces in the absence of filesystem metadata |
+| ``classify`` | The plugin categorizes a plugin based on its content, e.g. detecting money on traces of type `picture` |
+| ``digest`` | The plugin digests data to compute a hash |
+| ``ocr`` | The plugin applies ocr (optical character recognition) to read text on `pictures` or scanned documents |
+| ``match`` | The plugin matches a trace against a database, and reports whether there was hit or miss, e.g. matching a trace to a well known files database |
+
+**name**
+The name of the plugin, or in the classic sense, a description detailing what the plugin processes. Note that the name
+can contain (forward) slashes.
+
+## Examples
+
+The following table shows a list of plugin identifiers. The last column of the table shows the derived full plugin name.
+The derived full plugin name will be shown in Hansken.
+
+| Domain | Category | Name | Derived plugin name | Explanation |
+|-----------------|--------------|-----------------|------------------------------------|-------------------------------------------------------------------------------------------------------------|
+| ``hansken.org`` | ``extract`` | ``archive`` | ``hansken.org/extract/archive`` | A plugin created by the Hansken development team that extracts traces from an arbitrary ``archive`` format |
+| ``nfi.nl`` | ``extract`` | ``archive/zip`` | ``nfi.nl/extract/archive/zip`` | A plugin created by an NFI team that extracts traces from a specific ``archive`` format: ``zip`` |
+| ``politie.nl`` | ``extract`` | ``archive/zip`` | ``politie.nl/extract/archive/zip`` | The same as the previous example, but now the plugin is created by a different organisation: ``politie.nl`` |
+| ``hansken.org`` | ``carve`` | ``archive/zip`` | ``hansken.org/carve/archive/zip`` | A plugin that carves data to detect a specific ``archive`` format: ``zip`` |
+| ``hansken.org`` | ``digest`` | ``sha256`` | ``hansken.org/digest/sha256`` | A plugin that digests data to compute a ``sha256`` hash |
+| ``hansken.org`` | ``ocr`` | ``tesseract`` | ``hansken.org/ocr/tesseract`` | A plugin that performs ocr using ``tesseract`` |
diff --git a/0.9.16/_sources/dev/concepts/plugin_types.md.txt b/0.9.16/_sources/dev/concepts/plugin_types.md.txt
new file mode 100644
index 0000000..cb53fc9
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/plugin_types.md.txt
@@ -0,0 +1,77 @@
+# Extraction plugin types
+
+Currently four types of Hansken Extraction Plugins are supported:
+
+* Standard Extraction Plugins
+* Meta Extraction Plugins
+* Deferred Extraction Plugins
+* Deferred Meta Extraction Plugins (Python only)
+
+## Standard Extraction Plugins
+
+Standard Extraction Plugins provide enough functionality for most cases. This includes:
+
+* Processing traces
+* Creating child-traces
+* Reading trace properties
+* Adding trace properties
+* Reading data
+* Writing data
+* Writing [data transformations](data_transformations.md)
+
+## Meta Extraction Plugins
+
+Meta Extraction Plugins can only process and produce trace properties without the need (or possibility) for processing
+actual trace data. This includes:
+
+* Processing traces
+* Creating child-traces
+* Reading trace properties
+* Adding trace properties
+
+.. _deferred extraction plugins:
+
+## Deferred Extraction Plugins
+
+These plugins have two additional features that are not included with Standard Extraction Plugins:
+
+* a developer can choose to *defer* their execution
+* information about other traces can be obtained while processing the current extraction trace. Code examples can be
+ found here: :ref:`Java `, :ref:`Python `.
+
+*Deferring execution*
+A single Hansken image extraction consists of multiple *iterations*. Within every *iteration*, every Hansken tool and
+plugin is executed on matching traces, which produces new traces or modifies existing traces. If a tool can be executed
+another time because of these additions or modifications, another iteration is started.
+
+A regular plugin is executed in the same iteration a trace is matched. Deferred plugins are executed in a different
+iteration; they are always deferred for at least one iteration. This is very useful when searching for traces, because
+you are certain the deferred plugin is executed after all other tools performed their modifications.
+
+Sometimes, executing your plugin in the next iteration is not enough; it needs to be executed in a different iteration.
+This is why the SDK allows setting a *deferredIterations* parameter in the plugin info. After the plugin matches with a
+trace, it will be executed after *deferredIterations*. The execution can currently be deferred by a maximum of **20**
+iterations and the default is **1**.
+
+*Searching for traces*
+This type of plugin can perform a search to look for extracted traces in the current **image** (default) or
+**project** (optional). This search is performed using a provided **HQL** query.
+A maximum of **50** traces is returned for a given search request.
+
+.. warning:: Please note that HQL-Lite specific syntax such as the `Data` or `DataType` matchers is **NOT** supported.
+
+## Deferred Meta Extraction Plugins
+
+These plugins have two additional features that are not included with standard Extraction Plugins:
+
+* a developer can choose to *defer* their execution
+* information about other traces can be obtained while processing the current extraction trace. Code example can be
+ found here: :ref:`Python `.
+
+Deferred Meta Extraction Plugins can only process and produce trace properties without the need (or possibility) for processing
+actual trace data. This includes:
+
+* Processing traces
+* Creating child-traces
+* Reading trace properties
+* Adding trace properties
diff --git a/0.9.16/_sources/dev/concepts/test_framework.md.txt b/0.9.16/_sources/dev/concepts/test_framework.md.txt
new file mode 100644
index 0000000..a937c31
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/test_framework.md.txt
@@ -0,0 +1,276 @@
+# Test framework
+
+.. _test_framework:
+
+The SDK provides the _FLITS Test Framework_ for integration testing. This allows us to test/validate the plugin input
+and output without having a running Hansken instance.
+
+To use the test framework, three components are required:
+
+1. A running server instance of an extraction plugin. See section :ref:`How to test your plugin `.
+2. Input test data
+3. Results (expected output)
+
+## Creating test data
+
+The test data is independent of which programming language is used for the plugin (Java or Python). This section
+describes the setup of the test data, while the sections thereafter will link to the language specific documentation.
+
+.. _basictestdata:
+
+### Basic test data directory structure
+
+Example test data directory structure with an `inputs` and `results` directory:
+
+```text
+tests/
+├── inputs
+│ ├── example1.raw
+│ ├── example1.text
+│ ├── example1.trace
+│ ├── example2.raw
+│ └── example2.trace
+└── results
+ ├── example1.raw.PluginName.trace
+ ├── example1.text.PluginName.trace
+ └── example2.raw.PluginName.trace
+ ```
+
+The `inputs` folder contains all traces that will be processed during the test. These 'input traces' are defined in
+files with the '.trace' extension, using JSON. This JSON structure is explained in section
+:ref:`Trace format`. Each trace may have various :ref:`data-streams`. The data for each trace
+is put into separate files for each data-stream. The data-stream files need to have the same name as their corresponding
+trace file but differ in extension. They can have any extension, for example 'raw', 'text' or 'jpeg'. __Note that one
+input trace will always have one '.trace' file, and can have none, one or many data files.__ Also note that if the
+plugin doesn't match on any of the input files and there are no result files yet, the test will succeed.
+
+.. note:: The test-framework uses the extension of the input test file(s) _(other than __.trace__)_ as type of the
+ current data-stream.
+
+The expected results (which are also traces) are stored in a separate `results` folder next to the `inputs` folder. The
+file names in the `results` folder correspond to the file names in the `inputs` folder. Note that the name of the plugin
+is added between the file basename and the file extension. This can be useful if one maintains a single test input and
+output test datasets for multiple extraction plugins.
+
+.. note:: It is possible to let the test framework regenerate the results files automatically. See the
+ :ref:`Java` and :ref:`Python` sections on testing on how to do this. If no files are
+ being generated, check if the plugin matcher is actually matching the input files.
+
+The test runner will invoke the extraction plugin for each input trace. The test runner collects the plugin output and
+compares it against the trace defined in the `results` folder. If there is a mismatch, the test runner will fail with an
+exit code 1. If all tests pass the test runner will finish with exit code 0.
+
+Given the files in the example above, the test runner will invoke the extraction plugin three times:
+
+| Input | Result |
+|--------------------------------------------------------------|------------------------------------------------|
+| `example1.trace` with data `stream example1.raw` | `example1.raw.PluginName.trace` |
+| `example1.trace` with data `stream example1.text` | `example1.text.PluginName.trace` |
+| `example2.trace` with data `stream example2.text` | `example2.raw.PluginName.trace` |
+
+### Test data structure for deferred extraction plugins
+
+:ref:`Deferred extration plugins ` have the unique ability to search traces with a query.
+The `input` test data should be extended to contain the results of searches done by deferred extraction plugins. These
+_search traces_ are stored in separate folders that follow the naming format '{deferred trace name}/searchtraces/'. Below
+is an example test data directory structure for a deferred extraction plugin that searches for
+a `deferredExampleSearch.trace`:
+
+```text
+tests/
+├── inputs
+│ ├── deferredExample.trace
+│ ├── deferredExample.raw
+│ ├── deferredExample/
+│ │ ├── searchtraces/
+│ │ │ ├── deferredExampleSearch.trace
+└── results
+ └── deferredExample.raw.DeferredPluginName.trace
+```
+
+.. warning:: The plugin will try to match on all traces in the input folder, including traces used for search results (
+ of deferred extraction plugins). This means that it is impossible to search on traces that match the same deferred
+ extraction plugin, as it would create an infinite loop.
+
+Given the files in the example above, the test runner will invoke the extraction plugin one time:
+
+| Input | Result |
+|----------------------------------------------------------------|------------------------------------------------|
+| `deferredExample.trace` with data stream `deferredExample.raw` | `deferredExample.raw.DeferredPluginName.trace` |
+
+.. warning:: The search query should be written in HQL, as that is how Hansken will interpret it. However, the test
+ framework interprets the query using its HQL-lite interpreter. Therefore, not all queries will be supported.
+
+.. _traceformat:
+
+### Trace format
+
+Input and result traces both stored in a JSON structure. There is however a slight difference between the two: The
+result trace may store additional values that are purely there for testing purposes. The input format will first be
+discussed, followed by the result format.
+
+#### Input trace JSON format
+
+Input traces start with a `trace` key, which contains a mapping of properties. The property names are split in a
+dictionary structure. The example below shows a serialized trace with six properties: `data.raw.mimeClass` and the five
+data types that are currently supported by the test-framework.
+
+The `data` key defines the :ref:`data-streams ` of the trace. When adding a data-stream make sure you also
+add the corresponding input data file, as described :ref:`above`.
+
+```json
+{
+ "trace": {
+ "data": {
+ "raw": {
+ "mimeClass": "text"
+ }
+ },
+ "supported data types": {
+ "Boolean": true,
+ "Integer": 1,
+ "Double": 0.1,
+ "String": "a string",
+ "StringList": [
+ "a",
+ "b",
+ "c",
+ "d"
+ ]
+ }
+ }
+}
+```
+
+.. warning:: The extraction plugin SDK and the test framework have no knowledge of the
+ :ref:`trace model `. This means that when properties are used that don't
+ comply with the trace model, this will not cause the test to fail, but it will fail when running your plugin in Hansken.
+
+#### Result trace JSON format
+
+The result traces have the same format as the input traces, namely a `trace` key which contains the full input trace
+with all its properties. However, the result traces may have two additional keys `children` and `data` (which are
+explained in-depth below). These are added for testing purposes. If the plugin adds :ref:`child traces `
+or writes [data transformations](data_transformations.md) to Hansken, this would normally not reflect on the JSON of the
+trace. However, the test framework adds these to the result JSON structure to be able to test them.
+
+Consequently, result traces are stored in a JSON structure that may consist of up to three parts, namely the always
+present `trace` and the occasional `children` and `data`:
+
+* `trace`: The key `trace` contains a mapping of its properties, in exactly the same way as is done for input traces.
+* `children`: :ref:`Child traces ` that have been created by the plugin during the test are stored under a
+ reserved field `children`, which is a list of traces. The example trace below contains a child trace with a property
+ `name`.
+* `data`: [Data transformations](data_transformations.md) that have been created by the plugin during the test are
+ stored under a reserved field `data`. For each data-stream type there is a `descriptor` field describing the data
+ transformation in a JSON format. The example trace has a ranged data transformation for the raw data-stream. Note that
+ this `data` is entirely different from the `data` key that may be present _inside_ the `trace`!
+
+```json
+{
+ "trace": {
+ "data": {
+ "raw": {
+ "mimeClass": "text"
+ }
+ },
+ "supported data types": {
+ "Boolean": true,
+ "Integer": 1,
+ "Double": 0.1,
+ "String": "a string",
+ "List": [
+ "a",
+ "b",
+ "c",
+ "d"
+ ]
+ }
+ },
+ "children": [
+ {
+ "trace": {
+ "name": "child trace 1"
+ }
+ }
+ ],
+ "data": {
+ "raw": {
+ "descriptor": "[{\"ranges\":[{\"length\":79,\"offset\":0}]}]"
+ }
+ }
+}
+```
+
+### Testing exceptions
+
+Some scenarios may throw exceptions and this can be part of your tests too. For example, an input file that has the
+wrong format can be part of your integration tests. When an exception occurs during the test, it will be written to the
+result file. This can be deliberately used to test exceptions. However, it is often impractical to match against a full
+exception. For example, the row numbers in the exception are very much prone to change due to circumstances irrelevant
+to the case being tested. Therefore, the testframework provides some options to match only on those parts of result
+files that are relevant to the test.
+
+The following sections will explain these partial result matchers using the following example exception:
+
+```json
+{
+ "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
+ "message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris faucibus varius sodales."
+}
+```
+
+#### Leaving out the message
+
+It is possible to leave of the message of the exception, which will still result in a valid result:
+
+```json
+{
+ "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException"
+}
+```
+
+#### The `startsWith` partial result matcher
+
+The `startsWith` partial result matcher requires a string as a parameter. The result will be valid if the actual result
+starts with this string.
+
+```json
+{
+ "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
+ "message.startsWith": "Lorem ipsum dolor sit amet, "
+}
+```
+
+#### The `containsInOrder` partial result matcher
+
+The `containsInOrder` partial result matcher requires a list of strings as a parameter. The result will be valid if
+every string in the list can be found in that same order in the actual result.
+
+```json
+{
+ "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
+ "message.containsInOrder": [
+ "Lorem ipsum dolor sit amet,",
+ "consectetur adipiscing elit.",
+ "Mauris faucibus varius sodales."
+ ]
+}
+```
+
+.. _howtotestyourplugin:
+
+## How to test your plugin
+
+Running an integration test for an extraction plugin depends on the language in which the extraction plugin is built.
+
+### Java
+
+The Test Framework itself is built in Java. When building extraction plugins with Java, it can be incorporated in your
+unit tests, as shown in [Using the Test Framework in Java](../java/testing.md).
+
+### Python
+
+The Python SDK also uses the Java based Test Framework. This is done by providing a wrapper to make calls to an included
+Test Framework 'jar' file. See [Advanced use of the Test Framework in Python](../python/testing.md) for documentation
+and examples on how to use FLITS for testing your Python plugin.
diff --git a/0.9.16/_sources/dev/concepts/traces.md.txt b/0.9.16/_sources/dev/concepts/traces.md.txt
new file mode 100644
index 0000000..17de4b0
--- /dev/null
+++ b/0.9.16/_sources/dev/concepts/traces.md.txt
@@ -0,0 +1,213 @@
+# Traces & Trace model
+
+.. _traces:
+
+## Traces
+
+Traces are structured data objects produced by tools/plugins during an extraction. A trace represents a piece of
+information found in an evidence file.
+
+The following figure shows the main elements of a trace. Each element is described in more detail in the following
+paragraphs.
+
+
+
+.. The following diagram is used to produce the trace.png file
+
+ @startuml
+ hide empty methods
+
+ Interface Trace << (T,#FF7700 >> {
+ {static} String id
+ {static} String name
+ {static} String[] path
+ ..
+ Map properties
+ ..
+ List
+ List children
+ }
+
+ Interface DataStream << (D,orchid>> {
+ {static} String type
+ {static} String length
+ {static} byte[] data
+ ..
+ String fileType
+ Properties properties
+ }
+
+ Trace *-- Trace : a trace could have child traces
+ Trace *-- DataStream : a trace could have data streams
+
+ @enduml
+
+### Types and Properties
+
+A trace has properties that describe the information of it by means of a property value. Trace properties are grouped by
+a trace type. A trace can have multiple types.
+
+All types and properties that can be set are defined in the :ref:`Hansken trace model`.
+
+An example of a type is `document`, which could have the properties `application` and `createdOn`. The trace will have a
+type `document`, and can the following properties with values:
+
+ document.application: Libre Office
+ document.createdOn: 2021-09-18 20:00:00
+
+### Intrinsic properties
+
+A trace has several *intrinsic* properties. These are properties that are not related to a trace type. The intrinsic
+properties available to extraction plugins are:
+
+* `id`: a unique identifier of the trace, generated when a trace is created
+* `name`: a name given to a trace when it is created
+* `path`: a logical path of the trace, of which the elements are the names of traces from the root trace until this
+ trace
+
+.. _datastreams:
+
+### Data streams
+
+Typically, a trace represents a piece of data found in an evidence file. This data is part of the trace and available as
+a data stream. A trace can have multiple data streams. Each data stream has a type. Data streams can also have
+properties that apply to the data stream itself. The data stream properties are modeled as properties of the trace, in
+the following pattern:
+`data..propertyname` (where `` is substituted by the actual type of the data stream).
+
+The set of data stream types and data stream properties is fixed. All allowed types and properties are defined in the
+:ref:`Hansken trace model` (see `data`).
+
+An important data stream property is the `fileType` property. This property contains a textual description of the
+*detected* file type for the data stream. An example of a `fileType` is 'Adobe Pdf'. The `fileType` is a good candidate
+to use in a extraction plugin 'matcher'. This `fileType` is detected by Hansken using file type heuristics, which are
+primarily based on the data stream bytes itself, and secondarily on other metadata such as a file extension.
+(N.B. The `fileType` is detected in Hansken by the extraction tool `FireFli`.) For more information on how datastream
+properties can be used for matching, see :ref:`here`.
+
+Note that not all traces have data streams. In these cases it is a trace of meta-data derived from another trace.
+
+Usually, each trace with data has a data stream of type `raw`. This data stream contains the bytes of the traces as they
+were found when the trace was created. In some occasions, the `raw` data can be represented in a different form before it
+can be processed further, for example if the data can be decoded or decrypted. Hansken tools and plugins can decode
+the `raw` data stream to a standard UTF-8 data stream, or can decrypt the data if a decryption key is present. Hansken
+tools and extraction plugins can store the new data at the new trace in a new data stream. This new data stream has a
+different type than the `raw` type.
+
+Examples in code can be found here:
+
+* Adding a Datastream :ref:`Java `
+* Adding a Datastream :ref:`Python `
+
+.. TODO: Add a link to matcher in the paragraph above
+
+.. _child traces:
+
+### Child traces
+
+A trace can have child traces. For example, a trace of type `archive` can have children, where each child is a trace
+that represents an entry in the archive.
+
+With an extraction plugin it is possible to create child traces for a trace that is being processed. New properties,
+data streams, and other child traces can be set on the new child traces. When a child trace is created, the plugin
+should provide a `name` for the child trace. The `id` of the child trace is generated, in the following
+form: `parenttraceid-childnumber`. For example, if the parent has an id `0-0-0-0-0:0-9`, the first child gets the
+id `0-0-0-0-0:0-9-1`, the second child gets the id `0-0-0-0-0:0-9-2`, and so on.
+
+Note that a trace does not have (direct) access to its parent trace.
+
+### Trace property types
+
+The SDK supports the following property types for traces:
+
+| | Java | Python |
+|-------------|-----------------------|-----------------------|
+| binary | `byte[]` | `bytes` / `bytearray` |
+| boolean | `boolean` | `bool` |
+| integer | `int` / `long` | `int` |
+| real | `float` / `double` | `float` |
+| string | `String` | `str` |
+| date & time | `Date` | `datetime` |
+| list | `List` | `list` / `tuple` |
+| mapping | `Map` | `dict` |
+| location | `LatLong` | `GeographicLocation` |
+| vector | `Vector` | `Vector` |
+| tracelet | *see Tracelets below* | *see Tracelets below* |
+
+Both location and vector types are available from the SDK, Java package `org.hansken.plugin.extraction.api` or Python module `hansken.util`.
+
+.. _vector:
+
+#### Vector
+
+A vector is a data type that can be used to store points in n-dimensional space as an array of floating point values.
+Once indexed, the vectors can then be used in a gui or other client to search for traces that have a nearby vectors.
+For example, it is possible to use a neural network that provides embeddings of human faces as vectors. Once indexed,
+the vectors can then be used to find pictures with similar faces. To do this, the search rest api can be used to
+sort by the euclidean- or manhattan distance, or cosine similarity to a given vector.
+
+.. _tracelets:
+
+#### Tracelets
+
+A Tracelet is a bundle of property values that belong to a single type. It is a property on a trace that can have
+multiple properties itself, making it a list of key/value pairs. The API doesn't specify the cardinality, but the
+implementation is limited to cardinality 'Few'. In Hansken these are called FVT's (Few Valued Types).
+
+.. note:: MVT's (Many Valued Types) are currently not supported in the SDK and will be added in a future release.
+
+An example of a tracelet is the `prediction` property, which describes a category or class a trace belongs to. It is
+possible for a trace to have multiple predictions. Therefore `prediction` is a tracelet. Other examples of
+tracelets are `identity` and `collection`.
+
+Examples in code can be found here:
+
+* Adding tracelets in :ref:`Java `
+* Adding tracelets in :ref:`Python `
+
+.. _Hansken trace model:
+
+## Hansken trace model
+
+All traces in Hansken are based on a specific version of the trace model, and must comply to that version of the trace
+model. This is a nested data structure composed of origins, categories, types and properties.
+
+All non-inrinsic trace properties are optional and are grouped by **type**. These types are defined under the trace
+model section 'categories'. Every **category** has a list of allowed types. When a trace is identified as being a
+document, it will get this set of predefined document properties. Trace types can have different **origins**. The
+possible origins are defined in the trace model section 'origins'. An example of this is the processed types that are
+always generated by the system during an extraction.
+
+The details of the current trace model can be retrieved using the `/tracemodel`
+REST call on the Gatekeeper endpoint of Hansken, or check the Hansken Documentation on the trace model.
+
+.. TODO: nice screenshot on how to find the trace model in Hansken?
+
+.. _Trace model and the extraction plugin SDK:
+
+### Trace model and the extraction plugin SDK
+
+.. warning:: The extraction plugin SDK has no knowledge of the trace model
+
+The Extraction Plugins SDK has no knowledge of the trace model at this time. It is however possible to create new traces
+with plugins. If any newly created Traces don't comply to the model, Hansken will not accept them and mark the plugin
+execution as failed. The Extraction Plugins SDK and the provided [Test Framework](test_framework.md) don't check this.
+Please make sure to use the right naming when creating new Traces, as provided by the trace model.
+
+If an erroneous trace property is set, Hansken will show an error. The error can be found in the Hansken Expert UI
+interface by double-clicking on the trace. Then the trace details screen will be opened and the error will be displayed
+as follows:
+
+.. image:: toolrun_error.png
+
+This error describes that a property does not exist in the trace model. To get more information about the error, the
+extraction log can be viewed. In the extraction log you have to search
+for `java.lang.IllegalArgumentException: no such type` to find out which property is not supported by the trace model.
+
+In the example extraction log below, the property `this_property_does_not_exist` could not be found 681 times.
+
+ Cumulative warnings, based on the message without numbers, uuids and trace objects. Only showing full message for first warning of this type.
+ Count | Key | Message
+ 681 | org.hansken.ep.shade.io.grpc.StatusRuntimeException | CANCELLED: Cancelled by client with StreamObserver.onError(); org.hansken.ep.shade.io.grpc.StatusRuntimeException: ABORTED: java.lang.IllegalArgumentException: no such type: this_property_does_not_exist
+ 7 | java.lang.IllegalStateException | call was cancelled
+ 1 | org.hansken.ep.shade.io.grpc.StatusRuntimeException | UNAVAILABLE: HTTP/2 error code: NO_ERROR Received Rst Stream
diff --git a/0.9.16/_sources/dev/examples.md.txt b/0.9.16/_sources/dev/examples.md.txt
new file mode 100644
index 0000000..5be2f27
--- /dev/null
+++ b/0.9.16/_sources/dev/examples.md.txt
@@ -0,0 +1,5 @@
+# Examples
+
+Extraction Plugin Examples in both Java and Python can be found in the
+[Hansken Extraction Plugin Examples Repository](https://github.com/NetherlandsForensicInstitute/hansken-extraction-plugin-sdk-examples)
+hosted on Github.
diff --git a/0.9.16/_sources/dev/faq.md.txt b/0.9.16/_sources/dev/faq.md.txt
new file mode 100644
index 0000000..ee5a5f1
--- /dev/null
+++ b/0.9.16/_sources/dev/faq.md.txt
@@ -0,0 +1,64 @@
+# Frequently Asked Questions
+
+.. _communityaccess:
+
+## How can I access Hansken developer community
+
+You will first need git access to the Hansken developer community. Here you can find started guides and examples. If you
+have no access yet, you can get access by following the next steps:
+
+1. Sign up at [git.eminjenv.nl](https://git.eminjenv.nl/)
+2. After you have created your account, you should request access to the "
+ Hansken Community" group . Do this by contacting your organisations Hansken business owner. If you don't know who to
+ contact as your business owner, please read the :doc:`../contact` page.
+
+## Why use Extraction Plugins?
+
+Extraction plugins enable you to run your own extraction code in Hansken.
+
+* Your plugin runs during extraction
+* Extraction plugins are faster than Hansken.py
+* Multiple programming languages are supported
+* Scalability: Extraction plugins can be scaled flexibly on a kubernetes cluster
+
+## What programming languages are supported?
+
+The SDK contains an API and tools to write a Hansken extraction plugin in Java or Python. The Java API can be used to
+develop extraction plugins in JVM-compatible languages, such as Scala and Kotlin.
+
+## Will you support language *foobar*?
+
+Probably not. It takes time and effort to create a proper SDK. If you think there is a good use case to support
+language *foobar*, and there is gRPC support, feel free to contact us. We can discuss the options to add support for
+Hansken extraction plugins with *foobar*.
+
+Under the hood, extraction plugins use gRPC to communicate with Hansken. In theory, all programming languages that have
+a gRPC implementation can be used to write Hansken extraction plugins.
+
+## Can I reuse or modify the Extraction Plugins SDK?
+
+The SDK is distributed under the Apache 2.0 License, see the LICENSE file in the SDK for more details.
+
+## Can I use a plugin that someone else wrote?
+
+Yes, at your own risk. The Hansken Team does not take responsibility for code written by third parties in the form of
+Extraction Plugins.
+
+## What are the legal implications of creating your own Extraction Plugin(s)?
+
+All Extraction Plugins not written by the Hansken Team are considered **third party** Extraction Plugins. Please refer
+to [Can I use a plugin that someone else wrote?](faq.md#can-i-use-a-plugin-that-someone-else-wrote?)
+
+## How safe are Extraction Plugins?
+
+We are doing everything to make sure Extraction Plugins are as safe as possible, however note that the Extraction Plugin
+SDK is still in beta. Use it at your own risk. For more information on security see [Isolation](concepts/isolation.md).
+
+## Can my Extraction Plugin be embedded into Hansken for performance reasons?
+
+Embedding an Extraction Plugin into Hansken requires access to the Hansken source code. If you have access to the source
+code then please `../contact` us for assistance. Please note that embedded Extraction Plugins are **not officially
+supported**.
+
+If you do not have access to the Hansken source code, then please contact your own Business Owner, and ask them to
+contact the Hansken Team.
diff --git a/0.9.16/_sources/dev/introduction.md.txt b/0.9.16/_sources/dev/introduction.md.txt
new file mode 100644
index 0000000..7f99bec
--- /dev/null
+++ b/0.9.16/_sources/dev/introduction.md.txt
@@ -0,0 +1,50 @@
+# Introduction
+
+The Extraction Plugin Software Development Kit can be used to develop a [Hansken](https://www.hansken.nl/an-introduction-to-hansken)
+extraction plugin.
+
+Hansken is designed to give access to and insight in digital data and traces originating from seized and demanded
+digital material. One aspect of the Hansken platform is the extraction engine (extraction framework). The extraction
+engine contains digital forensics knowledge, which is used to find traces in digital material. With extraction plugins
+case investigators can add new digital forensics knowledge to the extraction framework. In this way, Hansken is enabled
+to understand new digital formats, and thus is able to find new types of traces in the seized and demanded material.
+
+Examples of digital forensics knowledge that can be added to Hansken with extraction plugins:
+
+* new file formats (e.g. a new crypto currency wallet)
+* combine traces to find new information (e.g. use a windows registry entry required to read a file from disk)
+* apply algorithms on traces (e.g. speech to text from audio files)
+
+The primary goals of this SDK are:
+
+* to make it as easy as possible to add new digital forensics knowledge to Hansken.
+* be able to share digital forensics knowledge with other Hansken community members
+
+## Software Development Kit (SDK)
+
+In order to create extraction plugins, the Hansken project maintains an Extraction Plugin Software Development Kit
+(SDK).
+
+This SDK contains the following elements:
+
+* [Java API and tooling](java), to be able to write an extraction plugin with the [Java](https://www.java.com/)
+ programming language
+* [Python API and tooling](python), to be able to write an extraction plugin with [Python](https://www.python.org/)
+ programming language
+* [Test framework](concepts/test_framework.md), to be able to test extraction plugins before they are used production
+* Documentation for plugin developers to help understand the SDK and all its facets (this documentation)
+* :doc:`examples`
+
+## Development steps of a plugin
+
+To create a plugin, the plugin developer could follow the following steps. Detailed information per step will be added
+later to the documentation.
+
+1. Create a new empty plugin
+2. Implement the plugin logic
+3. Verify the plugin using the [test framework](concepts/test_framework.md), using reference data (test data) and
+ expected output
+4. Test the plugin in Hansken
+5. Use the plugin in an actual case
+6. Share the plugin with the Hansken community, so other case investigations can benefit from the plugin as well (
+ optional, but encouraged)
diff --git a/0.9.16/_sources/dev/java.rst.txt b/0.9.16/_sources/dev/java.rst.txt
new file mode 100644
index 0000000..c6f37e9
--- /dev/null
+++ b/0.9.16/_sources/dev/java.rst.txt
@@ -0,0 +1,14 @@
+Java
+====
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Contents:
+
+ java/api_changelog
+ java/prerequisites
+ java/packaging
+ java/snippets
+ java/testing
+ java/debugging
+ java/javadoc
diff --git a/0.9.16/_sources/dev/java/api_changelog.md.txt b/0.9.16/_sources/dev/java/api_changelog.md.txt
new file mode 100644
index 0000000..fbca70d
--- /dev/null
+++ b/0.9.16/_sources/dev/java/api_changelog.md.txt
@@ -0,0 +1,292 @@
+# Java API Changelog
+
+This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that
+are important to plugin developers. For a full list of changes per version, please refer to the general
+:ref:`changelog `.
+
+.. If present, remove `..` before `## |version|` if you create a new entry after a previous release.
+
+.. ## |version|
+
+## 0.9.13
+
+* This release introduces a new parameter `bulkMode` to the `PluginInfo`. This can be used for
+ [lightweight plugins](snippets.md#bulk-mode) which have to process a lot of data (either a lot of traces or a small
+ number of traces with large data streams). These plugins will run inside the worker pod for streaming extractions,
+ and will therefore be able to process data more efficiently.
+
+## 0.9.11
+
+* This release allows the user to search for more than 50 traces using the `TraceSearcher` in deferred extraction plugins.
+
+## 0.9.10
+
+* This release introduces the deferred meta extraction plugin. This plugin type can *defer* their execution and
+ processes a trace only with its metadata, without processing its data and accesses traces using the searcher.
+ This makes it possible to use deferred plugins in combination with traces without data.
+ _Note:_ Hansken will support these types of plugins from v47.34.0.
+
+* This release introduces new parameters for the `TraceSearcher`, `start` and `sort`.
+ This allows the searcher can get a certain range of traces in a specific order.
+ _Note:_ Hansken will support these types of plugins from v47.34.0.
+
+## 0.9.5
+
+* The internal (de)serialization of some types has changed. Please update the extraction plugin sdk to match the one used in Hansken.
+
+## 0.8.0
+
+* The trace property `imageId` is renamed to `image`. This is to be in line with the Hansken REST API and Python API.
+ When updating your plugin, please update your calls `trace.get("imageId")` to `trace.get("image")`.
+
+* [#774](https://git.eminjenv.nl/hansken/hbacklog/-/issues/774)
+ By default, deferred extraction plugin searches are now scoped to the image
+ of the trace that is currently being processed. Optionally, a project-wide
+ search can be done by passing an optional scope argument.
+
+ ```java
+ @Override
+ public void process(final Trace trace, final ExtractionContext context, final TraceSearcher searcher) {
+ // only search for traces inside the same image as the trace that is being processed
+ final SearchResult result = searcher.search("file.extension=asc", 10);
+ final SearchResult result = searcher.search("file.extension=asc", 10, TraceSearcher.SearchScope.IMAGE);
+
+ // search for all traces inside the same project as the trace that is being processed
+ final SearchResult result = searcher.search("file.extension=asc", 10, TraceSearcher.SearchScope.PROJECT);
+ }
+ ```
+
+* Support trace properties of type `List`, `List`, and `List`.
+ This enables you to write multiple offsets and confidence scores in tracelets of type prediction.
+
+ For example:
+
+ ```java
+ trace.addTracelet("prediction", tracelet -> {
+ tracelet.set("modelName", "my_cat_detector");
+ tracelet.set("modelVersion", "0.0.BETA");
+ tracelet.set("type", "classification");
+ tracelet.set("label", "cat");
+
+ tracelet.set("offset", 3.0);
+ tracelet.set("confidence", 0.4);
+
+ tracelet.set("offsets", List.of(0.0, 3.0, 6.0, 9.0));
+ tracelet.set("confidences", List.of(0.1, 0.4, 0.03, 0.09));
+ })
+ ```
+
+## 0.7.0
+
+* Escaping the `/` character in matchers is optional.
+ This simplifies and aims for better HQL and HQL-Lite compatability.
+ See for more information and examples the :ref:`HQL-Lite syntax documentation`.
+
+ Examples:
+
+ * `file.path:\\/Users\\/*\\/AppData` -> `file.path:/Users/*/AppData`
+ * `registryEntry.key:\\/Software\\/Dropbox\\/ks*\\/Client-p` -> `registryEntry.key:/Software/Dropbox/ks*/Client-p`
+
+* Hansken returns `file.path` properties as a `String` property, instead of a `List`.
+ Example: `trace.get("file.path")` now returns `"/dev/null"`, this was `["dev", "null"]`.
+
+## 0.6.3
+
+* A plugin can now write multiple data streams to a single trace concurrently,
+ e.g. write both `decrypted` and `ocr` at the same time. See the ":ref:`datastreams java`" code snippets for
+ general examples on adding data to a trace.
+
+## 0.6.1
+
+* The JAVA SDK is now distributed through maven central instead of the Hansken community.
+
+## 0.6.0
+
+.. warning:: It is highly recommended to upgrade your plugin to this new version.
+ See the migration steps below.
+
+* Extraction plugin container images are now labeled with PluginInfo. This
+ allows Hansken to efficiently load extraction plugins.
+
+* By default, extraction plugin version is managed in the plugin's `pom.xml`.
+ The `.pluginVersion(..)` can be removed from the PluginInfo builder.
+
+* **Migration steps from earlier versions** -- for plugins that use the Java
+ extraction plugin SuperPOM:
+
+ 1. Update the SDK version in your `pom.xml`
+ 2. If you come from a version prior to `0.4.0`, or if you use a plugin name
+ instead of a plugin id in your `pluginInfo()`, switch to the plugin id style
+ (read instructions for version `0.4.0`)
+ 3. Set your plugin version in your project's `pom.xml`, and remove the
+ following from your `PluginInfo.Builder`:
+
+ ```java
+ .pluginVersion(...)
+ ```
+
+ 4. Update your build scripts to build your plugin (Docker) container image.
+ You should build your plugin container image with the following command:
+
+ ```bash
+ mvn package docker:build`
+ ```
+
+ This will generate a plugin image:
+
+ * The extraction plugin is added to your local image registry
+ (`docker images`),
+ * The image name is `extraction-plugin/PLUGINID`, e.g.
+ `extraction-plugin/nfi.nl/extract/chat/whatsapp`,
+ * The image is tagged with two tags: `latest`, and your plugin version.
+
+ Nb. If Docker is not available in your environment, `podman` can be used
+ as an alternative. See :ref:`packaging ` for more
+ details.
+
+## 0.5.0
+
+* Add new tracelet api `Trace.addTracelet(type, consumer)`.
+ It can be used like this:
+
+ ```java
+ trace.addTracelet("prediction", tracelet -> tracelet
+ .set("type", "classification")
+ .set("label", "label")
+ .set("confidence", 0.8f)
+ .set("embedding", Vector.of(1,2,3))
+ .set("modelName", "yolo")
+ .set("modelVersion", "2.0"));
+ ```
+
+* Deprecate Trace.addTracelet(Trace)
+* Support vector data type in trace properties.
+
+## 0.4.13
+
+* When writing input search traces for tests, it is no longer required to explicitly set an `id` property.
+ These are automatically generated when executing tests.
+
+## 0.4.7
+
+* A new convenience method `id(String, String, String)` is added to the PluginInfo builder. This removes some
+ boilerplate code when setting the pluginId. More details on the plugin naming conventions can be found at the
+ :doc:`../concepts/plugin_naming_convention` section.
+
+ ```java
+ PluginInfo.builderFor(this)
+ .id("nfi.nl", "extract", "TestPlugin") // new style
+ .id(new PluginId("nfi.nl", "extract", "TestPlugin")) // old style, but also works
+ ...
+ ```
+
+## 0.4.6
+
+* It is now possible to specify maximum system resources in the `PluginInfo`. To run a plugin with 0.5 cpu (= 0.5
+ vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to `PluginInfo`:
+
+ ```java
+ PluginInfo.builderFor(this)
+ ...
+ .pluginResources(PluginResources.builder()
+ .maximumCpu(0.5f)
+ .maximumMemory(1000)
+ .build())
+ .build();
+ ```
+
+## 0.4.0
+
+* Extraction Plugins are now identified with a `PluginInfo.PluginId` containing a domain, category and name. The
+ method `PluginInfo.name(pluginName)` has been replaced by `PluginInfo.id(new PluginId(domain, category, name)`. More
+ details on the plugin naming conventions can be found at the :doc:`../concepts/plugin_naming_convention` section.
+
+* `PluginInfo.name()` is now deprecated (but will still work for backwards compatibility).
+
+* A new license field `PluginInfo.license` has also been added in this release.
+
+* The following example creates a PluginInfo for a plugin with the name `TestPlugin`, licensed under
+ the `Apache License 2.0` license:
+
+ ```java
+ PluginInfo.builderFor(this)
+ .id(new PluginId("nfi.nl", "extract", "TestPlugin")) // id.domain: nfi.nl, id.category: extract, id.name: TestPlugin
+ // .name("TestPlugin") // no longer supported
+ .pluginVersion("0.4.1")
+ .author(Author.builder()...build())
+ .description("A plugin for testing.")
+ .maturityLevel(MaturityLevel.PROOF_OF_CONCEPT)
+ .hqlMatcher("*")
+ .webpageUrl("https://www.hansken.org")
+ .license("Apache License 2.0")
+ .build();
+ ```
+
+## 0.3.0
+
+* Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations
+ describe how data can be obtained from a source.
+
+ An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in
+ the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of
+ the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of
+ space is saved.
+
+ Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data
+ transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in
+ a bytearray.
+
+ The following example sets a new datastream with dataType `html` on a trace, by setting a ranged data transformation:
+
+ ```java
+ trace.setData("html", RangedDataTransformation.builder().addRange(offset, length).build());
+ ```
+
+ The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged
+ data transformation with two ranges:
+
+ ```java
+ trace.newChild(format("lineNumber %d", lineNumber), child -> {
+ child.setData("raw", RangedDataTransformation.builder()
+ .addRange(10, 20)
+ .addRange(50, 30)
+ .build());
+ });
+ ```
+
+ More detailed documentation will follow in an upcoming SDK release.
+
+## 0.2.0
+
+.. warning:: This is an API breaking change. Plugins created with an earlier version of the extraction plugin SDK are
+ not compatible with Hansken that uses `0.2.0` or later.
+
+* Introduced a new extraction plugin type `DeferredExtractioPlugin`. Deferred Extraction plugins can be run at a
+ different extraction stage. This type of plugin also allows accessing other traces using the searcher.
+
+* The class `ExtractionContext` has been renamed to `DataContext`. The new name `DataContext` represents the class
+ contents better. Plugins have to update matching import statements and the type in `ExtractionPlugin.process()`
+ implementation in the same way. This change has no functional side effects.
+
+ Old:
+
+ ```java
+ import org.hansken.plugin.extraction.api.ExtractionContext;
+
+ @Override
+
+ public void process(final Trace trace, final ExtractionContext context) throws IOException {
+
+ }
+ ```
+
+ New:
+
+ ```java
+ import org.hansken.plugin.extraction.api.DataContext;
+
+ @Override
+ public void process(final Trace trace, final DataContext dataContext) throws IOException {
+
+ }
+ ```
diff --git a/0.9.16/_sources/dev/java/debugging.md.txt b/0.9.16/_sources/dev/java/debugging.md.txt
new file mode 100644
index 0000000..2674eb4
--- /dev/null
+++ b/0.9.16/_sources/dev/java/debugging.md.txt
@@ -0,0 +1,134 @@
+# How to debug an Extraction Plugin
+
+Debugging is the art of removing bugs — hopefully quickly.
+
+## Locally
+
+To debug a plugin locally, it is recommended to start the plugin via the IDE by running the integration test. This has
+the advantage that breakpoints can easily be put in the code instead of printing log statements, for example.
+
+### Logging
+
+The logging of the extraction plugin is displayed in the console.
+
+## Locally with Docker
+
+Debugging an extraction plugin via docker is a bit trickier. Java has the advantage that remote debugging is already
+baked in.
+
+Using Java Remote Debug with Docker containers requires 3 distinct steps:
+
+1. Build a Docker image
+2. Run the Docker image with specific Java tool options
+3. Setting breakpoints in your code
+
+### Build a Docker image
+
+If the Docker image is not built, run the following command to build the Docker image:
+
+```bash
+mvn package docker:build
+```
+
+### Run the Docker image with specific Java tool options
+
+In Java, the remote debug functionality is not enabled by default. To enable the remote debug functionality, the
+following environments variable must be set in the Docker container:
+
+```bash
+JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
+ ```
+
+This environment variable allows the debugger to connect to the debuggee (application being debugged). To start the
+Docker image with the `JAVA_TOOL_OPTIONS` environment variable, the following command can be used:
+
+```bash
+docker run -p 5005:5005 -e JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" your_extraction_plugin_name
+```
+
+The next step is to attach the debugger to the debuggee.
+For Intellij, the instructions are clearly described on the following
+page: [Tutorial: Remote debug](https://www.jetbrains.com/help/idea/tutorial-remote-debug.html#49be7f04)
+
+### Setting breakpoints in the code
+
+The last step is to add breakpoints in the code.
+
+### Logging in Docker
+
+The logging of the extraction plugin is displayed in the console after running the `docker run` command. In addition,
+the logging is also displayed in the IntelliJ console while debugging.
+
+## Kubernetes
+
+In kubernetes it is currently _not_ possible to debug via Java Remote Debug because:
+
+* no debug ports are published;
+* the container was not started with the environment variable `JAVA_TOOL_OPTIONS` so debugging is not enabled.
+
+### Logging in Kubernetes
+
+If there is authorization to the kubernetes cluster, the logging can be viewed with the following command:
+
+```bash
+kubectl logs -f hansken-extraction-plugins/your_extraction_plugin_pod
+```
+
+## Debug HQL
+
+An HQL query can be debugged by overriding the `isVerboseLoggingEnabled()` method of the `ExtractionPluginFlits` class.
+The example below shows an example of an embedded FLITS test with verbose logging enabled.
+
+```java
+public class TestPluginFlitsIT extends EmbeddedExtractionPluginFlits {
+
+ @Override
+ public Path testPath() {
+ return srcPath("integration/inputs/plugin");
+ }
+
+ @Override
+ public Path resultPath() {
+ return srcPath("integration/results/embedded/plugin");
+ }
+
+ @Override
+ protected ExtractionPlugin pluginToTest() {
+ return new TestPlugin();
+ }
+
+ @Override
+ public boolean regenerate() {
+ return true;
+ }
+
+ @Override
+ protected boolean isVerboseLoggingEnabled() {
+ return true;
+ }
+}
+```
+
+The following output will then be displayed in the console:
+
+```text
+HQL match found for:
+$data.type=jpg
+With trace:
+dataType=jpg
+types={file, data}
+properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0}
+```
+
+If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query
+is ``$data.mimeType=image/jpg`` (slash not escaped). This query will produce an error like the one shown below.
+
+```json
+{
+ "class": "org.hansken.plugin.extraction.hql_lite.lang.ParseException",
+ "message": "HqlLiteHumanQueryParser: line 1:20 token recognition error at: '/jpg'"
+}
+```
+
+.. note:: The error is only shown in the generated trace, so to find out the `ParseException` override
+ the `regenerate()` method from `Flits` and then let this method return `true`.
diff --git a/0.9.16/_sources/dev/java/javadoc.md.txt b/0.9.16/_sources/dev/java/javadoc.md.txt
new file mode 100644
index 0000000..fdca19d
--- /dev/null
+++ b/0.9.16/_sources/dev/java/javadoc.md.txt
@@ -0,0 +1,3 @@
+# Javadoc
+
+Visit the `Javadoc of the Extraction Plugins SDK API <../../_static/javadoc/index.html>`_.
diff --git a/0.9.16/_sources/dev/java/packaging.md.txt b/0.9.16/_sources/dev/java/packaging.md.txt
new file mode 100644
index 0000000..0a51a06
--- /dev/null
+++ b/0.9.16/_sources/dev/java/packaging.md.txt
@@ -0,0 +1,42 @@
+# Packaging
+
+Extraction plugins are packaged as OCI images (also known as Docker images).
+The OCI images are labeled with the PluginInfo.
+To automate packaging of a Java plugin and labeling the OCI image, the Extraction Plugin SuperPom has been configured to automate this for you.
+
+If your project uses the Extraction Plugin SuperPom (see [Prerequisites](prerequisites.md)), Packaging an extraction plugin is handled by Maven.
+To package your plugin into a container image, the following command can be used:
+
+```bash
+mvn package docker:build
+```
+
+This will generate a plugin image:
+
+* The extraction plugin is added to your local image registry
+(`docker images`),
+* The image name is `extraction-plugin/PLUGINID`, e.g.
+`extraction-plugin/nfi.nl/extract/chat/whatsapp`,
+* The image is labeled with two tags: `latest`, and your plugin version.
+
+It is possible to apply extra arguments to the docker command [as described here](http://dmp.fabric8.io/#docker:build).
+For example, to specify a proxy, use the following command:
+
+```bash
+mvn package docker:build -Ddocker.buildArg.https_proxy=https://proxy:8001
+```
+
+Once your plugin is packaged, it can be published or 'uploaded' to Hansken.
+See ":ref:`upload_plugin`" for instructions.
+
+.. _java_superpom_podman:
+
+Note: if your build environment does not have Docker available, you can use
+[podman](https://podman.io/) as an alternative. Install podman on your machine
+or build agent, and run the following commands _before_ invoking the
+`mvn package docker:build` command:
+
+```bash
+podman system service --time=0 unix:/run/user/$(id -u)/podman/podman.sock &
+export DOCKER_HOST="unix:/run/user/$(id -u)/podman/podman.sock"
+```
diff --git a/0.9.16/_sources/dev/java/prerequisites.md.txt b/0.9.16/_sources/dev/java/prerequisites.md.txt
new file mode 100644
index 0000000..9a4279b
--- /dev/null
+++ b/0.9.16/_sources/dev/java/prerequisites.md.txt
@@ -0,0 +1,42 @@
+# Prerequisites
+
+Required software:
+
+* Java 11 or higher
+* Docker for [packaging](packaging.md) and publishing plugins
+ (or use a Docker alternative such as `podman`)
+* Maven (recommended, build automation tool)
+
+Required dependencies:
+* All required project dependencies to build extraction plugins are published on the public Maven Central, under `org.hansken.plugin.extraction:plugin-super-pom`.
+ For maven based extraction plugins, the following `pom.xml` snippet can be used as basis of a plugin:
+
+ ```xml
+
+
+ 4.0.0
+
+
+ org.hansken.plugin.extraction
+ plugin-super-pom
+ SET_THE_SDK_VERSION_HERE
+
+
+ CHOOSE_YOUR_ARTIFACTID_HERE
+ SET_THE_PLUGIN_VERSION_HERE
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ SET_THE_PLUGIN_MAIN_CLASS_HERE
+
+
+ ```
diff --git a/0.9.16/_sources/dev/java/snippets.md.txt b/0.9.16/_sources/dev/java/snippets.md.txt
new file mode 100644
index 0000000..6a5bfdd
--- /dev/null
+++ b/0.9.16/_sources/dev/java/snippets.md.txt
@@ -0,0 +1,265 @@
+# Java code snippets
+
+This page contains Java code snippets for common patterns that will be used when writing a plugin.
+
+## RandomAccessData as InputStream
+
+In Java, `InputStream` is a common type to pass data to another class or method. The SDK provides a simple utility to
+use a `RandomAccessData` as `InputStream`.
+
+Add the following import to your code:
+
+```java
+import org.hansken.plugin.extraction.core.data.RandomAccessDatas;
+```
+
+Next we can create an `InputStream` from the `RandomAccessData` as shown in the following snippet. Note that the
+`InputStream` is created using a `try-with-resources`-statement. This ensures that the `InputStream` is correctly closed
+when the `InputStream` is no longer required.
+
+```java
+RandomAccessData traceData=...;
+ try(InputStream asInputStream=RandomAccessDatas.asInputStream(traceData)){
+ // use the InputStream here
+ }
+```
+
+Notes:
+
+* the created `InputStream` is *not* thread-safe,
+* the created `InputStream` changes state in the provided `RandomAccessData`
+ (e.g. when data is read, the position updated in both the `InputStream` *and*
+ the `RandomAccessData` instances),
+* for more details on the implementation of the `InputStream`, refer to the `RandomAccessDataInputStream` JavaDoc.
+
+.. _tracelets java:
+
+## Adding tracelets
+
+In the following Java example, a "classification" :ref:`tracelet` is added to a trace. The tracelet consists
+of a list of four properties, namely "class", "confidence", "modelName" and "modelVersion".
+
+```java
+trace.addTracelet("prediction", tracelet -> tracelet
+ .set("type", "classification")
+ .set("class", "telephone")
+ .set("label", "label")
+ .set("confidence", 0.8f)
+ .set("embedding", Vector.of(1,2,3))
+ .set("modelName", "yolo")
+ .set("modelVersion", "2.0"));
+```
+
+or
+
+```java
+trace.addTracelet(new Tracelet("prediction", List.of(
+ new TraceletProperty("prediction.type","classification"),
+ new TraceletProperty("prediction.class","telephone"),
+ new TraceletProperty("prediction.label","label"),
+ new TraceletProperty("prediction.confidence",0.8f))),
+ new TraceletProperty("prediction.embedding", Vector.of(1,2,3)),
+ new TraceletProperty("prediction.modelName","yolo"),
+ new TraceletProperty("prediction.modelVersion","2.0"));
+```
+
+.. _datastreams java:
+
+## Adding data to a trace
+
+Traces can have data attached to them. See :ref:`datastreams` for more information.
+The following two snippets demonstrate how to add data to a trace.
+
+It is currently not possible to verify that a specific data stream is already set or not.
+
+### Data Transformations
+
+The most efficient way to add data to a trace is using data transformations.
+See :doc:`../concepts/data_transformations` for more details.
+
+The following example sets a new data stream with dataType `html` on a trace, by setting a ranged data transformation:
+
+```java
+trace.setData("html", RangedDataTransformation.builder().addRange(offset, length).build());
+```
+
+The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged
+data transformation with two ranges:
+
+```java
+trace.newChild(format("lineNumber %d", lineNumber), child -> {
+ child.setData("raw", RangedDataTransformation.builder()
+ .addRange(10, 20)
+ .addRange(50, 30)
+ .build());
+});
+```
+
+### Blobs
+
+It is not always possible to create a transormation for the data that has to be
+added to a trace. For example the data is a result of a computation, and not
+a direct subset of another data stream..
+
+The following examples show how to creates a new data stream of dataType `raw` on a trace.
+
+In case all data is stored in a `byte[]`, we can add the byte array to the data stream with:
+
+```java
+final byte[] rawBytes = {.....};
+trace.setData("raw", writer -> writer.write(rawBytes));
+```
+
+Alternatively, if the data is available in an `InputStream` the data can be added with:
+
+```java
+final InputStream inputStream = ...;
+trace.setData("raw", inputStream);
+```
+
+## Specifying system resources
+
+In the `PluginInfo` you can specify **maximum** system resource metrics for a plugin. These are used for scaling the
+number of pods as described [here](../concepts/kubernetes_autoscaling.md#Autoscaling). To run a plugin with 0.5 cpu (=
+0.5 vCPU/Core/hyperthread), 1 gb memory and 10 (concurrent) cpu workers (threads), for example, the following configuration can be added to `PluginInfo`:
+
+```java
+PluginInfo.builderFor(this)
+ ...
+ .pluginResources(PluginResources.builder()
+ .maximumCpu(0.5f)
+ .maximumMemory(1000)
+ .maximumWorkers(10)
+ .build())
+ .build();
+```
+
+.. _java_snippets_deferred:
+
+## Deferred Extraction Plugins
+
+Using a deferred plugin requires inheriting the `DeferredExtractionPlugin` base class. This allows access to
+a ``TraceSearcher`` object in the process function to search for traces.
+
+```java
+public class ExampleDeferred extends DeferredExtractionPlugin {
+ @Override
+ public PluginInfo pluginInfo();
+
+ @Override
+ public void process(final Trace trace, final ExtractionContext context, final TraceSearcher searcher) {
+ final SearchResult result = searcher.search("file.extension=asc", 10);
+ }
+}
+```
+
+The ``search`` method accepts three arguments;
+1. a HQL query (note: this is the traditional HQL query, and not the matchers HQL-lite variant),
+2. the maximum number of traces the return (currently hard-limited to a maximum of 50 traces),
+3. (optional) a scope, which can be either `TraceSearcher.SearchScope.IMAGE` (default), or `TraceSearcher.SearchScope.PROJECT`.
+ When set to `IMAGE`, the searcher will only search for traces within the same image as the trace that is being processed.
+
+The traces contained in the ``SearchResult`` are returned as a stream.
+
+```java
+final Stream stream = result.getTraces();
+stream.limit(5);
+```
+
+## Bulk Mode
+
+The `PluginInfo` contains a parameter `bulkMode`. This can be used for lightweight plugins which have to process a lot
+of data (either a lot of traces with data or a small number of traces with large data streams). For streaming
+extractions, these plugins will run inside the worker pod, and will therefore be able to process data more efficiently.
+
+**WARNING**: The plugin should be lightweight. This means that it should not use a lot of resources like CPU or memory,
+because this will limit the resources of the worker pod, and therefore Hansken will not be able to start enough workers
+to do extractions.
+
+Creating a plugin with bulk mode enabled can be done by setting the parameter in `PluginInfo` as follows:
+
+```java
+PluginInfo.builderFor(this)
+ ...
+ .bulkMode()
+ .build();
+```
+
+## Logging
+
+The logging is provided by Log4j 2 with a SLF4J binding. The Log4j 2 SLF4J binding allows applications coded to the
+SLF4J API to use Log4j 2 as the implementation.
+
+### Usage
+
+Here is an example illustrating how to log something with SLF4J. It begins by getting a logger with the name "LOG". This
+logger is in turn used to log the message `I'm logging a variable 1234!`.
+
+```java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Example {
+ private static final Logger LOG = LoggerFactory.getLogger(Example.class);
+
+ public void example() {
+ final int aNumber = 1234;
+ // logs to console: I'm logging a variable 1234!
+ LOG.info("I'm logging a variable {}!", aNumber);
+ }
+}
+```
+
+### Customize logging
+
+It's easy to change the logging format with a file called `log4j2.xml`. If desired, this file must be in the `resources`
+folder, for example `src/main/resources/log4j2.xml`
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+.. warning:: Be careful with logging sensitive information.
+
+.. note:: More information about customizing the logging can be found `here `_.
+
+.. note:: The default logger is pre-configured to log `INFO` to `STDOUT` (see the configuration above)
+
+.. note:: Log4j 2 supports various logging formats, including xml, yaml, json, properties, etc.
+ Currently, only the xml format is supported.
+
+.. note:: Contact your Hansken administrator for more information on where to find logs for your Hansken environment.
+
+## [EXPERIMENTAL FEATURE] Adding previews to a trace
+
+.. warning:: This is an experimental feature, which might change or get removed in future releases.
+
+Example:
+
+```java
+public class ExamplePlugin extends ExtractionPlugin {
+ @Override
+ public PluginInfo pluginInfo();
+
+ @Override
+ public void process(final Trace trace, final DataContext context) {
+ final byte[] previewData;
+ // set the preview data for the image/png MIME-type
+ trace.set("preview.image/png", previewData);
+ trace.set("preview.image/png", previewData);
+ }
+}
+```
diff --git a/0.9.16/_sources/dev/java/testing.md.txt b/0.9.16/_sources/dev/java/testing.md.txt
new file mode 100644
index 0000000..aff916c
--- /dev/null
+++ b/0.9.16/_sources/dev/java/testing.md.txt
@@ -0,0 +1,156 @@
+# Using the Test Framework in Java
+
+.. _java testing:
+
+This section assumes you use the same setup as is used in the
+[Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples).
+
+## Prerequisites
+
+Java Plugins can use the `plugin-super-pom` as maven parent, which makes sure the
+FLITS [Test Framework](../concepts/test_framework.md) is included in the build.
+
+```xml
+
+
+ org.hansken.plugin.extraction
+ plugin-super-pom
+ 0.4.3
+
+```
+
+## Embedded Testing versus Remote Testing
+
+There are ways of integration testing a plugin with the Test Framework:
+
+* **Embedded testing**: Here the plugin is run directly from a JUnit test without using the gRPC layer.
+* **Remote testing**: Here the test will start an ExtractionPluginServer that will serve the plugin. All communication
+ is between server and plugin is done using gRPC.
+
+See below for an example of each way of testing.
+The [Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples) contains many
+more examples.
+
+### Embedded Testing example
+
+Embedded tests can be run as a unit test.
+
+```java
+import static nl.minvenj.nfi.flits.util.FlitsUtil.srcPath;
+
+import java.nio.file.Path;
+
+import org.hansken.plugin.extraction.api.ExtractionPlugin;
+import org.hansken.plugin.extraction.test.EmbeddedExtractionPluginFlits;
+
+/**
+ * An integration test for MyPlugin.
+ */
+class MyPluginIT extends EmbeddedExtractionPluginFlits {
+
+ @Override
+ protected ExtractionPlugin pluginToTest() {
+ // MyPlugin is a class implementing the ExtractionPlugin interface,
+ // with pluginInfo() and process() methods.
+ return new MyPlugin();
+ }
+
+ @Override
+ public Path testPath() {
+ // Provide the folder containing input files. For examples, see
+ // https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+ return srcPath("integration/inputs");
+ }
+
+ @Override
+ public Path resultPath() {
+ // Provide the folder containing result files. For examples, see
+ // https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+ return srcPath("integration/results");
+ }
+
+ @Override
+ public boolean regenerate() {
+ // Returning false means the test will fail if the result files don't
+ // match the outcome of the=++ test. Returning true means the test create
+ // new result files .
+ return false;
+ }
+}
+```
+
+### Remote Testing example
+
+Note that the following example serves the plugin by using `ExtractionServer`.
+
+```java
+import static nl.minvenj.nfi.flits.util.FlitsUtil.srcPath;
+
+import java.nio.file.Path;
+
+import org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginClient;
+import org.hansken.plugin.extraction.runtime.grpc.server.ExtractionPluginServer;
+import org.hansken.plugin.extraction.test.plugins.DataTransformationsPlugin;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+public class RemoteTransformationPluginFlitsIT extends RemoteExtractionPluginFlits {
+
+ private static ExtractionPluginServer _server;
+ private static ExtractionPluginClient _client;
+
+ @BeforeAll
+ public static void init() throws Exception {
+ final int port = 8999;
+
+ // Serve MyPlugin.
+ // MyPlugin is a class implementing the ExtractionPlugin interface, with PluginInfo and Process methods.
+ _server = ExtractionPluginServer.serve(port, MyPlugin::new);
+
+ // Create an ExtractionPluginClient
+ _client = new ExtractionPluginClient("localhost", _server.getListeningPort());
+ }
+
+ @AfterAll
+ public static void destruct() {
+ // At the end of the test, make sure the server and client are closed.
+ if (_server != null) {
+ _server.close();
+ }
+ if (_client != null) {
+ _client.close();
+ }
+ }
+
+ @Override
+ public Path testPath() {
+ // Provide the folder containing input files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+ return srcPath("integration/inputs");
+ }
+
+ @Override
+ public Path resultPath() {
+ // Provide the folder containing result files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+ return srcPath("integration/results");
+ }
+
+ @Override
+ protected ExtractionPluginClient pluginToTest() {
+ // For Remote testing, the test won't talk directly to the plugin, but to the client.
+ // The client will use gRPC to communicate with the served plugin.
+ return _client;
+ }
+
+ @Override
+ public boolean regenerate() {
+ // Returning false means the test will fail if the result files don't match the outcome of the test.
+ // Returning true means the test create new result files.
+ return false;
+ }
+}
+```
+
+.. note:: Note that with a `RemoteTransformationPluginFlitsIT` it is possible to start a docker image of a plugin and
+ run remote tests against it using your own testdata. To do this, simply remove all `_server` code and manually start
+ your plugin in a docker container. Then run the test against the docker container by setting the correct url and
+ port, presumably `new ExtractionPluginClient("localhost", 8999)`.
diff --git a/0.9.16/_sources/dev/python.rst.txt b/0.9.16/_sources/dev/python.rst.txt
new file mode 100644
index 0000000..ee03dfb
--- /dev/null
+++ b/0.9.16/_sources/dev/python.rst.txt
@@ -0,0 +1,25 @@
+Python
+======
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Contents:
+
+ python/api_changelog
+ python/prerequisites
+ python/getting_started
+ python/packaging
+ python/snippets
+ python/transformers
+ python/testing
+ python/hanskenpy
+ python/debugging
+
+API Documentation
+-----------------
+
+.. autosummary::
+ :toctree: python/api
+ :recursive:
+
+ hansken_extraction_plugin.api
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.data_context.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.data_context.rst.txt
new file mode 100644
index 0000000..e49fcfd
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.data_context.rst.txt
@@ -0,0 +1,29 @@
+hansken\_extraction\_plugin.api.data\_context
+=============================================
+
+.. automodule:: hansken_extraction_plugin.api.data_context
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ DataContext
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.rst.txt
new file mode 100644
index 0000000..1f25f80
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.rst.txt
@@ -0,0 +1,33 @@
+hansken\_extraction\_plugin.api.extraction\_plugin
+==================================================
+
+.. automodule:: hansken_extraction_plugin.api.extraction_plugin
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ BaseExtractionPlugin
+ DeferredExtractionPlugin
+ DeferredMetaExtractionPlugin
+ ExtractionPlugin
+ MetaExtractionPlugin
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_trace.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_trace.rst.txt
new file mode 100644
index 0000000..01a389b
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_trace.rst.txt
@@ -0,0 +1,33 @@
+hansken\_extraction\_plugin.api.extraction\_trace
+=================================================
+
+.. automodule:: hansken_extraction_plugin.api.extraction_trace
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ ExtractionTrace
+ ExtractionTraceBuilder
+ MetaExtractionTrace
+ SearchTrace
+ Trace
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.plugin_info.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.plugin_info.rst.txt
new file mode 100644
index 0000000..84db660
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.plugin_info.rst.txt
@@ -0,0 +1,34 @@
+hansken\_extraction\_plugin.api.plugin\_info
+============================================
+
+.. automodule:: hansken_extraction_plugin.api.plugin_info
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ Author
+ MaturityLevel
+ PluginId
+ PluginInfo
+ PluginResources
+ TransformerLabel
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.rst.txt
new file mode 100644
index 0000000..cdb9470
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.rst.txt
@@ -0,0 +1,40 @@
+hansken\_extraction\_plugin.api
+===============================
+
+.. automodule:: hansken_extraction_plugin.api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+.. rubric:: Modules
+
+.. autosummary::
+ :toctree:
+ :recursive:
+
+ hansken_extraction_plugin.api.data_context
+ hansken_extraction_plugin.api.extraction_plugin
+ hansken_extraction_plugin.api.extraction_trace
+ hansken_extraction_plugin.api.plugin_info
+ hansken_extraction_plugin.api.search_result
+ hansken_extraction_plugin.api.search_sort_option
+ hansken_extraction_plugin.api.trace_searcher
+ hansken_extraction_plugin.api.tracelet
+ hansken_extraction_plugin.api.transformation
+ hansken_extraction_plugin.api.transformer
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.search_result.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.search_result.rst.txt
new file mode 100644
index 0000000..28a93a8
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.search_result.rst.txt
@@ -0,0 +1,29 @@
+hansken\_extraction\_plugin.api.search\_result
+==============================================
+
+.. automodule:: hansken_extraction_plugin.api.search_result
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ SearchResult
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.search_sort_option.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.search_sort_option.rst.txt
new file mode 100644
index 0000000..1918d88
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.search_sort_option.rst.txt
@@ -0,0 +1,30 @@
+hansken\_extraction\_plugin.api.search\_sort\_option
+====================================================
+
+.. automodule:: hansken_extraction_plugin.api.search_sort_option
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ Direction
+ SearchSortOption
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.trace_searcher.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.trace_searcher.rst.txt
new file mode 100644
index 0000000..91a0322
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.trace_searcher.rst.txt
@@ -0,0 +1,30 @@
+hansken\_extraction\_plugin.api.trace\_searcher
+===============================================
+
+.. automodule:: hansken_extraction_plugin.api.trace_searcher
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ SearchScope
+ TraceSearcher
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.tracelet.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.tracelet.rst.txt
new file mode 100644
index 0000000..c502877
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.tracelet.rst.txt
@@ -0,0 +1,29 @@
+hansken\_extraction\_plugin.api.tracelet
+========================================
+
+.. automodule:: hansken_extraction_plugin.api.tracelet
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ Tracelet
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.transformation.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.transformation.rst.txt
new file mode 100644
index 0000000..3d80bab
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.transformation.rst.txt
@@ -0,0 +1,31 @@
+hansken\_extraction\_plugin.api.transformation
+==============================================
+
+.. automodule:: hansken_extraction_plugin.api.transformation
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ Range
+ RangedTransformation
+ Transformation
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.transformer.rst.txt b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.transformer.rst.txt
new file mode 100644
index 0000000..688f4fb
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api/hansken_extraction_plugin.api.transformer.rst.txt
@@ -0,0 +1,29 @@
+hansken\_extraction\_plugin.api.transformer
+===========================================
+
+.. automodule:: hansken_extraction_plugin.api.transformer
+
+
+
+
+
+
+
+
+
+
+
+ .. rubric:: Classes
+
+ .. autosummary::
+
+ Transformer
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_sources/dev/python/api_changelog.md.txt b/0.9.16/_sources/dev/python/api_changelog.md.txt
new file mode 100644
index 0000000..5c7c991
--- /dev/null
+++ b/0.9.16/_sources/dev/python/api_changelog.md.txt
@@ -0,0 +1,536 @@
+# Python API Changelog
+
+This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that
+are important to plugin developers. For a full list of changes per version, please refer to the general
+:ref:`changelog `.
+
+.. If present, remove `..` before `## |version|` if you create a new entry after a previous release.
+
+.. ## |version|
+
+## 0.9.13
+
+* This release introduces a new parameter `bulk_mode` to the `PluginInfo`. This can be used for
+ [lightweight plugins](snippets.md#bulk-mode) which have to process a lot of data (either a lot of traces or a small
+ number of traces with large data streams). These plugins will run inside the worker pod for streaming extractions,
+ and will therefore be able to process data more efficiently.
+
+## 0.9.10
+
+* This release introduces new parameters for the `trace_searcher`, `start` and `sort`.
+ This allows the searcher can get a certain range of traces in a specific order.
+ _Note:_ Hansken will support these types of plugins from v47.34.0.
+
+* This release allows the user to search for more than 50 traces using the `GrpcTraceSearcher`. By specifying a count
+ greater than 50, results will be retrieved in batches of 50 (or less) until the desired count is achieved.
+ Setting the count to `None` (or omitting it) allows the `GrpcTraceSearcher` to retrieve all available traces.
+ This functionality is implemented in a buffered manner and is defined within `BatchedSearchResult`,
+ which replaces the now-removed `GrpcTraceResult`. _Note_: the search results are still limited by Elasticsearch
+ so no more than 100.000 results can be obtained.
+
+## 0.9.9
+
+* This release introduces the deferred meta extraction plugin. This plugin type can *defer* their execution and
+ processes a trace only with its metadata, without processing its data and accesses traces using the searcher.
+ This makes it possible to use deferred plugins in combination with traces without data.
+ _Note:_ Hansken will support these types of plugins from v47.34.0.
+
+## 0.9.5
+
+* The internal (de)serialization of some types has changed. Please update the extraction plugin sdk to match the one used in Hansken.
+
+## 0.9.4
+
+* This release supports providing all types of transformer arguments when using the `execute_transformer` script.
+
+## 0.9.2
+
+* This release introduces a simple flow-control mechanism that fixes `connection RST` errors that can occur when a
+ plugin produces traces too fast. Plugins built with this version are **not backwards compatible**.
+
+* Python plugins now support transformers, remote methods which can be executed using the Hansken REST API. More information can be found in [the docs](transformers.md).
+
+* The `build_plugin` and `label_plugin` utilities prematurely shut down containers if the building and labeling process takes too long causing the process for slow containers to fail. If your plugin takes a long time to start, you may want to increase the timeout before the script stops trying to connect and aborts the process of building the plugin. This can be done using the new optional `--timeout` argument. The default is set to 30 seconds.
+
+* The optional image name argument of `build_plugin` is changed to a flag. Build scripts can be updated using `--target-name DOCKER_IMAGE_NAME`.
+
+## 0.8.3
+
+* This release addresses important load balancing issues. Please use release `0.8.3` as a drop-in-replacement for releases `0.8.2` and `0.8.1`.
+
+## 0.8.2
+
+* ⚠️ This release is deprecated, please upgrade to `0.8.3`
+
+* The `build_plugin` utility has been updated and the deprecation status has been removed.
+ As with `label_plugin`, `build_plugin` now no longer requires a full (virtual) environment
+ with all plugin dependencies and resources. This will greatly reduce build times for plugins with
+ big dependencies and/or large models.
+
+ The first argument of the command (a pointer to your `plugin.py` file) has been removed.
+ Please do not forget to remove the first argument of `build_plugin` in your `tox.ini` or other build tooling.
+
+ For usage read further in [packaging](packaging.md).
+
+* The default read-buffer of `trace.open('rb')` as been changed from 1 Megabyte to 6 Megabyte to reduce overhead while data reading.
+
+* The data stream writer of `trace.open('wb')` is now buffered as well. This means that multiple small writes will be flushed after every 6 Megabytes of data has been written (or when the writer is closed).
+
+* The read-buffer or write-buffer size can be overridden by the user, by passing the `buffer_size=` argument to `trace.open()`:
+
+ ```python
+ with trace.open('rb', buffer_size=1024*1024): # set a 1 Megabyte buffer size
+ pass
+
+ with trace.open('wb', buffer_size=1024*1024*12): # set a 12 Megabyte buffer size
+ pass
+
+ with trace.open('wb', buffer_size=1): # a buffer_size of 1 effectively disables the buffer:
+ pass # each write will be flushed to Hansken directly
+ ```
+
+* It is now possible to write `str` values to `trace.open(..)`. To do so, pass `mode='w'` as additional argument.
+ By default, it is assumed that the written text is 'utf-8' encoded. The default can be overwritten by using the `'encoding='` argument.
+
+ In a future Hansken update, Hansken will set the correct data-stream properties for your text stream (`mimeType`, `mimeClass`, and `fileType`).
+
+ Example use cases are:
+
+ * write picture-to-text (OCR) data to a trace
+ * write translations to a trace
+ * write audio-to-text (audio transcriptions) to a trace
+ * write the results of a JSON dump, e.g.: `json.dump(['your', 'data'], text_writer)`
+
+ Examples in code:
+
+ ```python
+ with trace.open(data_type='raw', mode='w', encoding='utf-8') as text_writer:
+ text_writer.write('hello.world') # write strings directly to it
+ json.dump({'hello': 'world'}, text_writer) # or pass the writer to json.dump
+ ```
+
+ See also :ref:`the python code snippet`.
+
+## 0.8.1
+
+* ⚠️ This release is deprecated, please upgrade to `0.8.3`
+
+## 0.8.0
+
+* The trace property `imageId` is renamed to `image`. This is to be in line with the Hansken REST API and Python API.
+ When updating your plugin, please update your calls `trace.get('imageId')` to `trace.get('image')`.
+
+* [#774](https://git.eminjenv.nl/hansken/hbacklog/-/issues/774)
+ By default, deferred extraction plugin searches are now scoped to the image
+ of the trace that is currently being processed. Optionally, a project-wide
+ search can be done by passing an optional scope argument.
+
+ ```python
+ def process(trace, data_context, searcher):
+ # only search for traces inside the same image as the trace that is being processed
+ searcher.search('*')
+ searcher.search('*', scope='image') # explicit alternative, using a str
+ searcher.search('*', scope=SearchScope.image) # explicit alternative, using the SearchScope enum
+
+ # only search for traces inside the same image as the trace that is being processed
+ searcher.search('*', scope='project')
+ searcher.search('*', scope=SearchScope.project)
+ ```
+
+* Support trace properties of type `list[float]`. This enables you to write
+ multiple offsets and confidence scores in tracelets of type prediction.
+
+ For example:
+
+ ```python
+ trace.add_tracelet('prediction', {
+ 'modelName': 'my_cat_detector',
+ 'modelVersion': '0.0.BETA',
+ 'type': 'classification',
+ 'label': 'cat',
+
+ # the best score
+ 'offset': 3.0,
+ 'confidence': 0.4,
+
+ # all scores
+ 'offsets': [0.0, 3.0, 6.0, 9.0],
+ 'confidences': [0.1, 0.4, 0.03, 0.09],
+ })
+ ```
+
+## 0.7.3
+
+* This version introduces a new docker image build utility `label_plugin`.
+ This utility will eventually replace `build_plugin`. `build_plugin` is now deprecated.
+
+ `label_plugin` is a utility to add labels to an extraction plugin image. Labeling a plugin is required for
+ Hansken to detect extraction plugins in a plugin image registry.
+
+ To label a plugin, first build the plugin image with [docker build](https://docs.docker.com/reference/cli/docker/image/build/);
+ for example by using one of the following commands:
+
+ ```shell
+ docker build . -t my_plugin
+ docker build . -t my_plugin --build-arg https_proxy=http://your_proxy:8080
+ ```
+
+ Next, run the `label_plugin` utility to label the build plugin container:
+
+ ```shell
+ label_plugin my_plugin
+ ```
+
+ The result of `label_plugin` is a plugin image that can be :ref:`uploaded to Hansken`.
+
+ `label_plugin` is preferred over `build_plugin`, as it does not require a full (virtual) environment
+ with all plugin dependencies and resources. This is especially preferred when the plugin uses (big)
+ data models or (external) dependencies.
+
+ For usage read further in [packaging](packaging.md).
+
+## 0.7.0
+
+* Escaping the `/` character in matchers is optional.
+ This simplifies and aims for better HQL and HQL-Lite compatability.
+ See for more information and examples the :ref:`HQL-Lite syntax documentation`.
+
+ Examples:
+
+ * Old: `file.path:\/Users\/*\/AppData` -> new: `file.path:/Users/*/AppData`
+ * Old: `file.path:\\/Users\\/*\\/AppData` -> new: `file.path:/Users/*/AppData`
+ * Old: `registryEntry.key:\/Software\/Dropbox\/ks*\/Client-p` -> new: `registryEntry.key:/Software/Dropbox/ks*/Client-p`
+
+* Hansken returns `file.path` properties (outside the scope of matchers) as a `String` property,
+ instead of a list of strings.
+ Example: `trace.get('file.path')` now returns `'/dev/null'`, this was `['dev', 'null']`.
+
+* Improved plugin loading when using `serve_plugin` and `build_plugin`:
+ `import` statements now work for modules (python files) that are located the same directory structure of a plugin.
+
+* A plugin can now stream data to a trace using `trace.open(mode='wb')`.
+ This removes the limit on the size of data that could be written.
+ See also :ref:`the python code snippet`.
+
+ Example:
+
+ ```python
+ with trace.open(mode='wb') as writer:
+ writer.write(b'a string')
+ writer.write(bytes(another_string, 'utf-8'))
+ ```
+
+ _note_: this does not work when using `run_with_hanskenpy`.
+
+## 0.6.1
+
+* The docker image build script `build_plugin` has been updated to allow for extension of the docker command.
+ This can be especially handy for specifying a proxy. You should build your plugin container image with the following
+ command:
+
+ ```bash
+ build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY [DOCKER_IMAGE_NAME] [DOCKER_ARGS]
+ ```
+
+ .. warning:: Note that the `DOCKER_IMAGE_NAME` argument no longer requires a `-n` parameter to be specified.
+
+ For usage read further in [packaging](packaging.md).
+
+## 0.6.0
+
+.. warning:: This is an API breaking change.
+ Upgrading your plugin to this version will require code changes.
+ Plugins built with previous versions of the SDK from `0.3.0` will still work with Hansken.
+
+.. warning:: It is strongly recommended to upgrade your plugins to this new version because it significantly improves
+ the start-up time of Hansken. See the migration steps below.
+
+This release contains both build pipeline changes and API changes.
+Please read all changes carefully.
+
+### Build pipeline change
+
+* Extraction plugin container images are now labeled with PluginInfo. This
+ allows Hansken to efficiently load extraction plugins.
+ Migration steps from earlier versions:
+
+ 1. Update the SDK version in your `setup.py` / `requirements.txt`
+ 2. If you come from a version prior to `0.4.0`, or if you use a plugin name
+ instead of a plugin id in your `pluginInfo()`, switch to the plugin id style
+ (read instructions for version `0.4.0`)
+ 3. Update your build scripts to build your plugin (Docker) container image.
+ Be sure to [have the Extraction Plugins SDK installed](getting_started.md#Installation).
+ Then, you should build your plugin container image with the following command:
+
+ ```bash
+ build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY -n [DOCKER_IMAGE_NAME]
+ ```
+
+ For example:
+
+ ```bash
+ build_plugin plugin/chatplugin.py . -n extraction-plugins/chatplugin
+ ```
+
+ This will generate a plugin image:
+
+ * The extraction plugin is added to your local image registry (`docker images`),
+ * Note that DOCKER\_IMAGE\_NAME is optional and will default to `extraction-plugin/PLUGINID`, e.g.
+ `extraction-plugin/nfi.nl/extract/chat/whatsapp`,
+ * The image is tagged with two tags: `latest`, and your plugin version.
+
+### API changes
+
+* The field `plugin` has been removed from `PluginInfo`.
+* The field `pluginId` should now be the first argument of PluginInfo (when using unnamed arguments).
+
+ Old (unnamed arguments):
+
+ ```python
+ def plugin_info(self):
+ return PluginInfo(self, '1.0.0', 'description', author,
+ MaturityLevel.PROOF_OF_CONCEPT, '*, 'https://hansken.org',
+ PluginId(...), 'Apache License 2.0')
+ ```
+
+ New (removed `self`, and moved `PluginId(...)` to first argument position):
+
+ ```python
+ def plugin_info(self):
+ return PluginInfo(PluginId(...), '1.0.0', 'description',
+ author, MaturityLevel.PROOF_OF_CONCEPT,
+ '*', 'https://hansken.org', 'Apache License 2.0')
+ ```
+
+ Old (named arguments):
+
+ ```python
+ def plugin_info(self):
+ return PluginInfo(plugin=self,
+ version='1.0.0',
+ ...)
+ ```
+
+ New (removed `plugin=self`):
+
+ ```python
+ def plugin_info(self):
+ return PluginInfo(version='1.0.0',
+ ...)
+ ```
+
+* Plugin `data_context.data_size` is now a variable instead of a method:
+
+ Old:
+
+ ```python
+ def process(self, trace: ExtractionTrace, data_context: DataContext):
+ size = data_context.data_size()
+ ```
+
+ New:
+
+ ```python
+ def process(self, trace: ExtractionTrace, data_context: DataContext):
+ size = data_context.data_size
+ ```
+
+* Simplify declaring required runtime resources in a plugin's info.
+
+ Extraction plugin resources don't use the builder pattern anymore.
+
+ Old:
+
+ ```python
+ return PluginInfo(
+ ...,
+ resources=PluginResources.builder().maximum_cpu(0.5).maximum_memory(1000).build())
+ )
+ ```
+
+ New:
+
+ ```python
+ # no need for a builder, declare resources by direct instantiation
+ return PluginInfo(
+ ...,
+ resources=PluginResources(maximum_cpu=2.0, maximum_memory=2048)
+ )
+ # or, as before, specify just on resource
+ return PluginInfo(
+ ...,
+ resources=PluginResources(maximum_memory=4096)
+ )
+ ```
+
+## 0.5.1
+
+* Simplify tracelet properties by making the tracelet type prefix optional.
+
+ ```python
+ # using a Tracelet object
+ trace.add_tracelet(Tracelet("prediction", {
+ "type": "example",
+ "confidence": 0.8
+ }))
+ # or without a Tracelet object
+ trace.add_tracelet("identity", {"name": "John Doe", "status": "online"})
+ ```
+
+* Enabled _manual_ plugin testing, as described on :ref:`advanced use of the test framework in Python`.
+
+## 0.5.0
+
+* Support vector data type in trace properties.
+
+ ```python
+ embedding = Vector.from_sequence((width, height))
+ tracelet = Tracelet("prediction", {
+ "prediction.type": "example-vector",
+ "prediction.embedding": embedding
+ })
+ trace.add_tracelet(tracelet)
+ ```
+
+## 0.4.13
+
+* When writing input search traces for tests, it is no longer required to explicitly set an `id` property.
+ These are automatically generated when executing tests.
+
+## 0.4.7
+
+* More `$data` matchers are supported in Hansken.py plugin runner. Before this improvement it was only possible to match
+ on `$data.type`. Now it is also possible to match for example on `$data.mimeType` and `$data.mimeClass`. The `$data`
+ matcher should still be at the end of the query as before.
+
+## 0.4.6
+
+* It is now possible to specify maximum system resources in the `PluginInfo`. To run a plugin with 0.5 cpu (= 0.5
+ vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to `PluginInfo`:
+
+ ```python
+ plugin_info = PluginInfo(...,
+ resources=PluginResources.builder().maximum_cpu(0.5).maximum_memory(1000).build())
+ ```
+
+## 0.4.0
+
+* Extraction Plugins are now identified with a `PluginInfo.PluginId` containing a domain, category and name. The
+ method `PluginInfo.name(pluginName)` has been replaced by `PluginInfo.id(new PluginId(domain, category, name)`. More
+ details on the plugin naming conventions can be found at the :doc:`../concepts/plugin_naming_convention` section.
+
+* `PluginInfo.name()` is now deprecated (but will still work for backwards compatibility).
+
+* A new license field `PluginInfo.license` has also been added in this release.
+
+* The following example creates a PluginInfo for a plugin with the name `TestPlugin`, licensed under
+ the `Apache License 2.0` license:
+
+ ```python
+ class TestPlugin(ExtractionPlugin):
+ def plugin_info(self) -> PluginInfo:
+ return PluginInfo(self,
+ version='1.0.0',
+ description='A plugin for testing.',
+ author=Author('The Externals', 'tester@holmes.nl', 'NFI'),
+ maturity=MaturityLevel.PROOF_OF_CONCEPT,
+ webpage_url='https://hansken.org',
+ matcher='file.extension=txt',
+ id=PluginId(domain='nfi.nl', category='test', name='TestPlugin'),
+ license='Apache License 2.0'
+ )
+ ```
+
+## 0.3.0
+
+* Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations
+ describe how data can be obtained from a source.
+
+ An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in
+ the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of
+ the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of
+ space is saved.
+
+ Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data
+ transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in
+ a bytearray.
+
+ The following example sets a new datastream with dataType `html` on a trace, by setting a ranged data transformation:
+
+ ```python
+ trace.add_transformation('html', RangedTransformation(Range(offset, length)))
+ ```
+
+ The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged
+ data transformation with two ranges:
+
+ ```python
+ child = trace.child_builder('new trace')
+ child.add_transformation('raw', RangedTransformation.builder()
+ .add_range(10, 20)
+ .add_range(50, 30)
+ .build())
+ });
+ ```
+
+ More detailed documentation will follow in an upcoming SDK release.
+
+## 0.2.0
+
+.. warning:: This is an API breaking change.
+ Plugins created with an earlier version of the extraction plugin
+ SDK are not compatible with Hansken that uses `0.2.0` or later.
+
+* Introduced a new extraction plugin type `api.extraction_plugin.DeferredExtractioPlugin`.
+ Deferred Extraction plugins can be run at a different extraction stage.
+ This type of plugin also allows accessing other traces using the searcher.
+
+* The class `api.extraction_context.ExtractionContext` has been renamed to `api.data_context.DataContext`.
+ The new name `DataContext` represents the class contents better.
+ Plugins have to update matching import statements accordingly.
+ Plugins should also update the named argument `context` to `data_context` of the plugin `process()` method.
+ This change has no functional changes.
+
+ Old:
+
+ ```python
+ from hansken_extraction_plugin.api.extraction_context import ExtractionContext
+
+ def process(self, trace, context):
+ pass
+
+ ```
+
+ New:
+
+ ```python
+ from hansken_extraction_plugin.api.data_context import DataContext
+
+ def process(self, trace, data_context):
+ pass
+ ```
+
+* Moved `api.author.Author` to `api.plugin_info.Author`, and moved `api.maturity_level.MaturityLevel`
+ to `api.plugin_info.MaturityLevel`
+ This is a more *pythonic* way of grouping of classes into modules. This change has no functional side effects.
+
+ Plugins have to update matching import statements accordingly.
+
+ Old:
+
+ ```python
+ from hansken_extraction_plugin.api.author import Author
+ from hansken_extraction_plugin.api.maturity_level import MaturityLevel
+ from hansken_extraction_plugin.api.plugin_info import PluginInfo
+ ```
+
+ New:
+
+ ```python
+ from hansken_extraction_plugin.api.plugin_info import Author, MaturityLevel, PluginInfo
+ ```
+
+* Removed `DataContext.get_first_bytes()` from the public API.
+
+* Removed `api.extraction_trace.validate_update_arguments(..)` from the public API. This method is still invoked
+ implicitly when setting trace properties.
diff --git a/0.9.16/_sources/dev/python/debugging.md.txt b/0.9.16/_sources/dev/python/debugging.md.txt
new file mode 100644
index 0000000..b4bdf6d
--- /dev/null
+++ b/0.9.16/_sources/dev/python/debugging.md.txt
@@ -0,0 +1,161 @@
+# How to debug an Extraction Plugin
+
+Debugging is the art of removing bugs — hopefully quickly.
+
+## Locally
+
+To debug a plugin locally, it is recommended to start the plugin via the IDE. This has the advantage that breakpoints
+can easily be put in the code instead of printing log statements, for example. To start a plugin locally, a piece of
+code must be added, see [Testing](testing.md) for more information.
+
+### Logging
+
+The logging of the extraction plugin is displayed in the console.
+
+## Locally with Docker
+
+Debugging an extraction plugin via docker is a bit trickier. In order to debug in Python, a debugger must be added to
+the extraction plugin. There are several debug modules for Python available, but one debug module that works well with
+Visual Studio Code is [debugpy](https://github.com/microsoft/debugpy). This package is developed by Microsoft
+specifically for use in Visual Studio Code with Python.
+
+.. note:: `debugpy` implements the Debug Adapter Protocol (DAP), which is a standardised way for development tools to
+ communicate with debuggers.
+
+Using `debugpy` with Docker containers requires 4 distinct steps:
+
+1. Install `debugpy`
+2. Configuring `debugpy` in Python
+3. Build a docker image
+4. Configuring the connection to the Docker container
+5. Setting breakpoints in your code
+
+### Install `debugpy`
+
+First, add `debugpy` to your `setup.py`.
+
+```python
+from setuptools import setup
+
+setup(
+ # ...
+ install_requires=[
+ "hansken-extraction-plugin==0.4.7", # the plugin SDK
+ "debugpy==1.5.1"
+ ]
+)
+```
+
+### Configuring `debugpy` in Python
+
+At the beginning of your script, import `debugpy`, and call `debugpy.listen()` to start the debug adapter, passing
+a `(host, port)` tuple as the first argument. Use the `debugpy.wait_for_client()` function to block program execution
+until the client is attached.
+
+```python
+import debugpy
+
+debugpy.listen(("0.0.0.0", 5678))
+debugpy.wait_for_client() # blocks execution until client is attached
+
+# your extraction plugin code
+```
+
+### Build a Docker image
+
+If the Docker image is not built, first build the image as described
+[here](getting_started.md#Building a docker image for a Python plugin).
+
+### Configuring the connection to the Docker container
+
+`debugpy` is now set up to accept connections inside a Docker container. To connect to `debugpy` in the docker
+container, port 5678 must be published. To make a port available to services outside of Docker, use the --publish or -p
+flag. This creates a firewall rule which maps a container port to a port on the Docker host to the outside world.
+
+To run the extraction plugin with the published port the following command can be used:
+
+```bash
+docker run -p 5678:5678 your_extraction_plugin_name
+```
+
+The next step is to configure Visual Studio Code. A `launch.json` file must be created in order for Visual Studio Code
+to connect to the extraction plugin in Docker. This minimal `launch.json` example below tells the debugger to attach
+to `localhost` on port `5678`.
+
+```json
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Remote Attach",
+ "type": "python",
+ "request": "attach",
+ "connect": {
+ "host": "localhost",
+ "port": 5678
+ },
+ "pathMappings": [
+ {
+ "localRoot": "${workspaceFolder}",
+ "remoteRoot": "."
+ }
+ ]
+ }
+ ]
+}
+```
+
+### Setting breakpoints in the code
+
+The last step is to add breakpoints in the code.
+
+### Logging in Docker
+
+The logging of the extraction plugin is displayed in the console after running the `docker run` command. In addition,
+the logging is also displayed in the Visual Studio Code console while debugging.
+
+## Kubernetes
+
+In kubernetes it is currently _not_ possible to debug via `debugpy` because no debug ports are published.
+
+### Logging in Kubernetes
+
+If there is authorization to the kubernetes cluster, the logging can be viewed with the following command:
+
+```bash
+kubectl logs -f hansken-extraction-plugins/your_extraction_plugin_pod
+```
+
+## Debug HQL
+
+An HQL query can be debugged by running the test framework with the `--verbose` option enabled. The found HQL matches
+will then be displayed in the console. To test a plugin in python with the `--verbose` option enabled use the following
+command:
+
+```bash
+test_plugin --standalone plugin/your_plugin.py --regenerate --verbose
+```
+
+The following output will then be displayed in the console:
+
+```text
+HQL match found for:
+$data.type=jpg
+With trace:
+dataType=jpg
+types={file, data}
+properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0}
+```
+
+If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query
+is ``$data.mimeType=image/jpg`` (slash not escaped). This query will produce an error like the one shown below.
+
+```json
+{
+ "class": "org.hansken.plugin.extraction.hql_lite.lang.ParseException",
+ "message": "HqlLiteHumanQueryParser: line 1:20 token recognition error at: '/jpg'"
+}
+```
+
+.. note:: The error is only shown in the generated trace, so to find out the `ParseException` run the ``test_plugin``
+ command with the ``--regenerate`` option enabled.
diff --git a/0.9.16/_sources/dev/python/getting_started.md.txt b/0.9.16/_sources/dev/python/getting_started.md.txt
new file mode 100644
index 0000000..be01bf3
--- /dev/null
+++ b/0.9.16/_sources/dev/python/getting_started.md.txt
@@ -0,0 +1,193 @@
+# Getting started
+
+Set up a development environment: step by step.
+
+The following section describes how to set up a fully working development environment for extraction plugins with Python.
+This is written for those who are not comfortable setting up a working build environment.
+This is **optional**; advanced users may choose a different development environment setup, and can skip this section completely.
+
+If you fail to set up a development environment, feel free to ask for help at our Discord channel.
+
+## Install required software on Ubuntu
+
+In order to be able to develop Hansken Extraction Plugins in Python on Ubuntu, the following build tools need be installed on your system: python, pip, tox, Java, Docker.
+
+* **Python, pip, tox, Java**
+ To install, run the following commands in your terminal:
+
+ ```bash
+ sudo apt update
+ sudo apt install python3.10 python3-pip tox default-jdk
+ ```
+
+ You can check whether all versions are installed correctly by running the following commands (and validate the output):
+
+ ```bash
+ python --version
+ # should return version 3.10.15 or higher
+
+ pip3 --version
+ # should return version 20.0.2 or higher
+
+ tox --version
+ # should return version 3.11.2 or higher
+
+ java -version
+ # should return version 11.0.4 or higher
+ ```
+
+If the above software is installed, you can continue with "Install Docker",
+or if you don't want to install Docker, continue with "Install your IDE: Pycharm".
+
+## Install required software on Windows
+
+In order to be able to develop Hansken Extraction Plugins in Python on Windows, follow the next steps:
+
+* For verification of the installation of each program we will use commands on the command prompt.
+ This can be opened by clicking the Windows Start button and typing:
+
+ ```bash
+ cmd
+ ```
+
+ Then hit Enter . This will open the command prompt where you can enter the commands given in the following steps.
+
+* **Python 3.10 or higher & pip**
+ Download the installer from [python.org/Downloads](https://www.python.org/downloads/) (click the yellow "Download Python" button)
+ and run it to install Python and pip.
+ Pip is the standard package manager for Python.
+ It allows you to install and manage additional packages that are not part of the Python standard library, like the Extraction Plugins SDK.
+
+ Be sure to select the option "Add python to PATH".
+
+ When the installation is complete, verify the installation by checking the Python and pip versions:
+
+ ```bash
+ python --version
+ # should return your downloaded Python version (>3.10.4)
+
+ pip3 --version
+ # should return 20.2.3 or higher
+ ```
+
+* **tox**
+ Tox is used to automate and standardize testing in Python. Use Pip to install Tox with this command:
+
+ ```bash
+ pip install tox
+ ```
+
+ Verify that Tox is installed by running:
+
+ ```bash
+ tox --version
+ # should return 3.23.0 or higher
+ ```
+
+* **Java JDK 11.0.4 (or higher)**
+ Java JDK 11.0.4 or higher is needed to run the test framework of the Extraction Plugins SDK.
+ This enables you to test without actually deploying the plugin in Hansken.
+ Installing Java on Windows can be done in many different way, but for now we can not recommend one method.
+ Please have a look at [Install the Microsoft Build of OpenJDK](https://docs.microsoft.com/en-us/java/openjdk/install) for more details.
+
+ ```bash
+ java -version
+ # should return 11.0.4 or higher
+ ```
+
+ N.b. Make sure to set the environment variable in case the installed JDK is not detected by the system.
+ You can follow the below-mentioned steps to set the environment variable:
+
+ 1. Click the Windows start button
+ 2. Type "advanced system settings"
+ 3. Hit enter
+ 4. Now click on Environment Variables, select Path under System Variables section and click on Edit. We need to add the path of installed JDK to system Path.
+ 5. Click on New Button and add the path to installed JDK bin which is C:\java\java-11\jdk-11.0.4\bin in our case.
+ 6. Press OK Button 3 times to close all the windows. This sets the JDK 11 on system environment variables to access the same from the console.
+
+If the above software is installed, you can continue with "Install Docker",
+or if you don't want to install Docker, continue with "Install your IDE: Pycharm".
+
+## Install Docker (Ubuntu, Windows)
+
+Installing Docker is a bit more complicated. The Docker website describes the installation instructions in detail.
+Please follow the instructions on [docs.docker.gom/get-docker/](https://docs.docker.com/get-docker/) to install docker.
+
+* To check you have Docker installed correctly, run
+
+ ```bash
+ docker --version
+ # should return version 20.10.17 or higher
+ ```
+
+Note: if you run Docker inside a managed network, you might also need to configure proxies and/or certificates.
+Please contact your system administrator if you need help with this.
+
+## Set up your IDE: PyCharm
+
+We recommend that you use an IDE to aid you in your development of Extraction Plugins. PyCharm is a good choice.
+
+* JetBrains has an excellent installation guide on their webpage: [Click here to go to the Pycharm Installation Guide](https://www.jetbrains.com/help/pycharm/installation-guide.html)
+
+## Download an extraction plugin template (empty plugin)
+
+You can download an extraction plugin template.
+This is an empty plugin, from which you can rapidly start your plugin development.
+
+* The template is hosted on GitHub [here](https://github.com/NetherlandsForensicInstitute/hansken-extraction-plugin-template-python).
+ You can download a zip with the template from here. The below screenshot shows where the download button is located:
+
+ 
+
+## Import the Extraction Plugins Skeleton in PyCharm
+
+* First unzip the skeleton plugin downloaded from the previous step.
+* Next, start PyCharm.
+* When PyCharm starts, choose "Open" and select the folder where you placed the Extraction Plugin Skeleton.
+
+
+* The following popup will appear, click OK .
+
+
+* The Extraction Plugins Skeleton is now loaded in PyCharm, which should look as follows:
+
+
+* Be sure to give the `README.md` a read when you are done with the Prerequisites.
+
+## Verify full setup
+
+To verify that your system has been setup correctly, you can run the test suite in the Extraction Plugin Skeleton:
+
+* First, press `alt-F12` at the same time to open a terminal in PyCharm in the project root folder.
+* To run the tests of the Skeleton, run this command in the terminal from the root folder:
+
+ ```bash
+ tox
+ ```
+
+ The first time running tox may take a few minutes! Please be patient 😊
+
+ Tox will install all required plugin dependencies, and start your tests.
+ N.b. The plugin template demonstrates how to build a plugin with tox.
+ Some plugin developers choose a different tool than tox.
+
+* If your system has been set up correctly, the output should end with a summary like this:
+
+ ```bash
+ py38: commands succeeded
+ congratulations :)
+ ```
+
+ This means the setup is finished. You now have everything installed to start coding your own plugin!
+
+## Next steps
+
+Now that you have a working environment, you can start doing cool stuff.
+Please have a look at the following pages for more information:
+
+* [Packaging](packaging.md): how to package your plugin and use it in Hansken
+* [Run plugins with Hansken.py](hanskenpy.md): run your plugin on a case without uploading the plugin to Hansken, useful for quick prototyping
+* [Transformers for on-demand execution](transformers.md): how to write a transformer to execute code on-demand outside extraction time.
+* [Testing](testing.md): how to write tests for your plugin
+* [Debugging](debugging.md): if your plugin isn't working as expected, you can debug it
+* [Snippets](snippets.md): code snippets to demonstrate common plugin usage patterns
diff --git a/0.9.16/_sources/dev/python/hanskenpy.md.txt b/0.9.16/_sources/dev/python/hanskenpy.md.txt
new file mode 100644
index 0000000..32d3022
--- /dev/null
+++ b/0.9.16/_sources/dev/python/hanskenpy.md.txt
@@ -0,0 +1,91 @@
+# Run plugins with Hansken.py
+
+Hansken.py is a Python client to Hansken's REST API, developed and maintained by the Netherlands Forensic Institute.
+
+With Hansken.py, you can run your Python plugin on a project on your Hansken installation that has already been
+extracted. This way of running your plugin is useful during your plugin development. It is not required to upload your
+plugin to Hansken and start an extraction, making the development cycle faster. However, please note that this is only
+useful during the development stage of your plugin, as the execution of your plugin will be much slower compared to
+running the plugin during a Hansken extraction.
+
+.. note:: The execution of your plugin will be much slower in Hansken.py compared to running the plugin during a Hansken
+ extraction.
+
+## How to run python extraction plugins standalone with Hansken.py
+
+Running python extraction plugins standalone with Hansken.py is easy. It is just one command. This section explains how
+to setup this command for your specific environment.
+
+### Create a runner file
+
+Create a file `run_with_hansken.py` in the root folder of your plugin. This will aid you in running the plugin with
+hansken.py.
+
+```python
+from hansken_extraction_plugin.runtime.extraction_plugin_runner import run_with_hanskenpy
+
+from plugin.my_plugin import MyPlugin
+
+if __name__ == '__main__':
+ run_with_hanskenpy(MyPlugin)
+```
+
+### Preparing for the command
+
+Before you can enter the command that runs your extraction plugin with Hansken.py, you need to find three values:
+
+* `HANSKEN_PROJECT_ID` project id on which you want to run your plugin
+* `YOUR_GATEKEEPER_URL` the URL to the Hansken gatekeeper
+* `YOUR_KEYSTORE_URL` the URL to your keystore
+
+The correct values of these variables can be found in the Expert UI. Go to the search-page of your project in the
+ExpertUI. Next to the search-bar hit the button "Save query".
+
+
+
+A new dialog shows up. At the bottom of this dialog, you will find your gatekeeper and keystore urls as well as the
+project id.
+
+
+
+### Running your plugin with Hansken.py
+
+Next, open a terminal in the project root folder of your plugin and enter the following command. It will run your
+extraction plugin with Hansken.py in Hansken. Replace the three variables with their respective values.
+
+```bash
+python3 ./run_with_hanskenpy.py -v -l - HANSKEN_PROJECT_ID --endpoint YOUR_GATEKEEPER_URL --keystore YOUR_KEYSTORE_URL
+```
+
+If your command runs well, you might be prompted for your username and password. There will be some output (note that
+the output may vary depending on your system setup and project content):
+
+```text
+[2021-03-16 12:59:45.344248+0000] INFO: hansken.auth: selected IDP ID (...) with SOAP endpoint (...)
+[2021-03-16 12:59:45.344450+0000] WARNING: hansken.auth: IDP url known, user+pass auth required but no username supplied
+username []: testaccount
+[2021-03-16 12:59:48.423245+0000] INFO: hansken.auth: user acknowledged environment username or supplied custom username: testaccount
+password for user testaccount:
+[2021-03-16 12:59:53.799668+0000] INFO: hansken.auth: identity provider url and user+pass provided or known, using Keycloak SAML with Basic auth
+[2021-03-16 12:59:53.805538+0000] INFO: hansken_extraction_plugin.runtime.extraction_plugin_runner: PluginRunner is running plugin class Plugin
+[2021-03-16 12:59:53.859299+0000] INFO: hansken.auth: posting SAML request with authorization for user testaccount to IDP endpoint (...)
+[2021-03-16 12:59:54.240290+0000] INFO: plugin.extraction_plugin: processing trace 54197e67-8135-40c3-93f1-3d73a5552693
+[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace OCRimage
+[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace (...)
+```
+
+Note that the arguments `-v` and `-l -` are passed to enable logging. To find out what other options can be passed to
+this command, please have a look at the hansken.py documentation, or simply run the following command:
+
+```bash
+python3 ./run_with_hanskenpy.py --help
+```
+
+## Compatibility
+
+At this moment, running Extraction Plugins with Hansken.py has a few limitations. These are:
+
+* When writing an Extraction Plugin for use with Hansken.py, the matcher must contain exactly one "$data.property =
+ value" expression.
+* [Data transformations](../concepts/data_transformations.md) are currently not supported by Hansken.py.
+* :ref:`Tracelets` are not yet supported by the SDK in use with Hansken.py.
diff --git a/0.9.16/_sources/dev/python/packaging.md.txt b/0.9.16/_sources/dev/python/packaging.md.txt
new file mode 100644
index 0000000..a6096a9
--- /dev/null
+++ b/0.9.16/_sources/dev/python/packaging.md.txt
@@ -0,0 +1,136 @@
+# Packaging
+
+Extraction plugins are packaged as OCI images (also known as Docker images).
+The OCI images are _labeled_ with the PluginInfo.
+To automate packaging of a Python plugin and labeling the OCI image,
+the Extraction Plugin SDK comes with three utility applications: `label_plugin`, `build_plugin` and `build_plugin_ci`.
+
+To package a plugin, make sure that the Extraction Plugins SDK is [installed](getting_started.md#Installation), as well as Docker.
+Next build and label your plugin as described in the following sections.
+
+To verify that the image has been built, use the following command to view all local images:
+
+```bash
+docker images
+```
+
+Once your plugin is packaged and labelled, it can be published or 'uploaded' to Hansken.
+See ":ref:`upload_plugin`" for instructions.
+
+## `label_plugin`
+
+`label_plugin` is a utility to add labels to an extraction plugin image.
+To label a plugin, first build the plugin image with [docker build](https://docs.docker.com/reference/cli/docker/image/build/);
+for example by using one of the following commands:
+
+```shell
+docker build . -t my_plugin
+docker build . -t my_plugin --build-arg https_proxy=http://your_proxy:8080
+```
+
+Next, run the `label_plugin` utility to label the build plugin container:
+
+```shell
+label_plugin my_plugin
+```
+
+This utility will briefly start your plugin using Docker, and requests the PluginInfo from the plugin.
+The information from the PluginInfo will be added as [labels](https://docs.docker.com/config/labels-custom-metadata/) to the plugin image.
+The result of `label_plugin` is a plugin image that can be published to a docker/OCI image registry.
+
+## `build_plugin`
+
+The `build_plugin` extends `label_plugin` by also taking care of the docker build command.
+Use this as an one-liner to both build and label your plugin image.
+
+To build your plugin container image you can use the following command:
+
+```bash
+build_plugin DOCKER_FILE_DIRECTORY [DOCKER_IMAGE_NAME] [DOCKER_ARGS]
+```
+
+For example:
+
+```bash
+build_plugin .
+```
+
+and to pass proxy configurations to Docker:
+
+```bash
+build_plugin . --build-arg http_proxy="$http_proxy" --build-arg https_proxy="$https_proxy"
+```
+
+This will generate a plugin image:
+* The extraction plugin is added to your local image registry (`docker images`),
+* Note that the variables `$http_proxy` and `$https_proxy` are put in quotes, this is needed in case they contain
+ spaces,
+* The image is tagged with two tags: `latest`, and your plugin version.
+
+Arguments:
+* `DOCKER_FILE_DIRECTORY`: Path to the directory containing the Dockerfile of the plugin.
+* (Optional) `DOCKER\_IMAGE\_NAME`: Name of the docker image without tag. Note that docker image names cannot start with
+ a period or dash. If it starts with a dash, it will be interpreted as an additional docker argument (see
+ DOCKER\_ARGS). If no name is given the name defaults to `extraction-plugin/PLUGINID`, e.g.
+ `extraction-plugin/nfi.nl/extract/chat/whatsapp`.
+* (Optional) `DOCKER\_ARGS`: Additional arguments for the docker command, which can be as many arguments as you like.
+
+## `build_plugin_ci`
+
+The `build_plugin_ci` performs the same tasks as `build_plugin` except that it uses a different approach for labeling
+the plugin. Not all CI/CD pipelines allow docker containers to be started and connected to, something which
+`build_plugin` relies on to label the plugin correctly using the plugin info specified in the plugin. `build_plugin_ci`
+uses another approach which exports the image and then parses the plugin info from this image. This way a container
+does not have to be started. Therefore the advantage of `build_plugin_ci` is that it can more reliably build plugins on
+CI systems. The downside of this approach is that it is slower than `build_plugin` utility. It is therefore adviced to
+use `build_plugin` when developing locally to speed up the development process and to use `build_plugin_ci` when
+building plugins in CI/CD pipelines.
+
+### Requirements
+
+To build your plugin container image using `build_plugin_ci` you have to add the following line to your Containerfile
+after you have copied your plugin to the image. `build_plugin_ci` requires this to find the plugin information.
+
+
+```
+RUN plugin_info "/app/plugin.py"
+```
+
+### Command usage
+
+`build_plugin_ci` can be invoked as follows:
+
+```bash
+build_plugin DOCKER_FILE_DIRECTORY [--target_name DOCKER_IMAGE_NAME] [--build_agent BUILD_AGENT] [BUILD_AGENT_ARGS]
+```
+
+Other than the options provided by `build_plugin` `build_plugin_ci` also provides a `--build_agent` flag which allows a
+user to choose between different build agents to build and label their plugin. Currently only docker, podman and
+buildah are supported. For example:
+
+```bash
+build_plugin . --target_name my-image:1.3.5 --build_agent "podman"
+```
+
+A proxy configurations can be configured as follows:
+
+```bash
+build_plugin . --build-arg http_proxy="$http_proxy" --build-arg https_proxy="$https_proxy"
+```
+
+This will generate a plugin image:
+* The extraction plugin is added to your local image registry (`docker images`),
+* Note that the variables `$http_proxy` and `$https_proxy` are put in quotes, this is needed in case they contain
+ spaces,
+* The image is tagged with two tags: `latest`, and your plugin version.
+
+Arguments:
+* `DOCKER_FILE_DIRECTORY`: Path to the directory containing the Dockerfile of the plugin.
+* (Optional) `DOCKER\_IMAGE\_NAME`: Name of the docker image without tag. Note that docker image names cannot start with
+ a period or dash. If it starts with a dash, it will be interpreted as an additional docker argument (see
+ DOCKER\_ARGS). If no name is given the name defaults to `extraction-plugin/PLUGINID`, e.g.
+ `extraction-plugin/nfi.nl/extract/chat/whatsapp`.
+* (Optional) `BUILD\_AGENT`: The build agent that should be used to build the plugin. Either docker, podman or buildah.
+ Docker is the default.
+* (Optional) `BUILD\_AGENT\_ARGS`: Additional arguments for the buildagent command, which can be as many arguments as
+ you like.
diff --git a/0.9.16/_sources/dev/python/prerequisites.md.txt b/0.9.16/_sources/dev/python/prerequisites.md.txt
new file mode 100644
index 0000000..5a0cfd3
--- /dev/null
+++ b/0.9.16/_sources/dev/python/prerequisites.md.txt
@@ -0,0 +1,9 @@
+# Prerequisites
+
+All required project dependencies to build extraction plugins are published on the public [PyPI](https://pypi.org/project/hansken-extraction-plugin/).
+
+Required:
+
+* Python 3.10 or higher
+* Java 11 (for running the test-framework, which is implemented in Java)
+* Docker (for packaging and deploying extraction plugins in containers, can be omitted if you have an external build pipeline that provides Docker)
diff --git a/0.9.16/_sources/dev/python/snippets.md.txt b/0.9.16/_sources/dev/python/snippets.md.txt
new file mode 100644
index 0000000..1df84d4
--- /dev/null
+++ b/0.9.16/_sources/dev/python/snippets.md.txt
@@ -0,0 +1,286 @@
+# Python code snippets
+
+## Adding properties to a trace
+
+Use :py:meth:`update `
+to add trace types and their properties to an
+:py:class:`ExtractionTrace `.
+Example:
+
+```python
+def process(self, trace, data_context):
+ # get the name of the file
+ file_name = trace.get('file.name')
+ # set the chat application property on the trace
+ trace.update('chatConversation.application', f'DemoApp {file_name}')
+```
+
+All types and properties that can be set are defined in the :ref:`Hansken trace model`.
+
+### Date properties
+
+When adding a property which holds a value of data-type Date, always define timezone as being UTC. Example:
+
+```python
+def process(self, trace, data_context):
+ trace.update('file.modifiedOn',
+ datetime.fromtimestamp(1630510809, tz=timezone.utc))
+```
+
+### Category for extra properties
+
+If the information, which must be added as a property, does not match any of the existing properties of Hansken trace
+model, use the category "misc" (miscellaneous). When part of the category "misc", any name can be given to a property.
+The values of miscellaneous properties are expected to be of data-type string. Example:
+
+```python
+def process(self, trace, data_context):
+ trace.update({
+ 'file.misc.notes': 'Some additional notes about the file trace.',
+ 'file.misc.anyName': 'Even more notes.'
+ })
+```
+
+.. _tracelets python:
+
+### Adding tracelets
+
+In the following Python example, a "prediction" :ref:`tracelet` is added to a trace. The tracelet consists
+of a list of four properties, namely "class", "confidence", "modelName" and "modelVersion".
+
+```python
+trace.add_tracelet(Tracelet('prediction', {'class': 'telephone',
+ 'confidence': 0.8,
+ 'modelName': 'yolo',
+ 'modelVersion': '2.0'}))
+```
+
+## Adding child traces to a trace
+
+Adding child traces to the trace can be done by creating a builder with
+:py:meth:`child_builder `.
+Example:
+
+```python
+def process(self, trace, data_context):
+ child_builder = trace.child_builder('childTrace-1')
+ child_builder.update({
+ 'chatMessage.application': 'DemoApp',
+ 'chatMessage.from': 'Ann',
+ 'chatMessage.to': ['Mark'],
+ # list, because there can be multiple receivers
+ 'chatMessage.message': 'Hello, are you there?',
+ }).build()
+ grandchild_builder = child_builder.child_builder('grandchild')
+ grandchild_builder.update(data={'byte': b'some bytes'})
+ grandchild_builder.build()
+```
+
+This adds a single child trace with name `childTrace-1` with four properties and a grandchild trace with name
+`grandchild` and a byte data stream.
+
+.. _datastreams python:
+
+## Adding data to a trace
+
+Traces can have data attached to them. See :ref:`datastreams` for more information.
+The following two snippets demonstrate how to add data to a trace.
+
+It is currently not possible to verify that a specific data stream is already set or not.
+
+### Data Transformations
+
+The most efficient way to add data to a trace is using data transformations.
+See :doc:`../concepts/data_transformations` for more details.
+
+The following example sets a new datastream with dataType `html` on a trace, by setting a ranged data transformation:
+
+```python
+trace.add_transformation('html', RangedTransformation(Range(offset, length)))
+```
+
+The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged
+data transformation with two ranges:
+
+```python
+child = trace.child_builder('new trace')
+child.add_transformation('raw', RangedTransformation.builder()
+ .add_range(10, 20)
+ .add_range(50, 30)
+ .build())
+});
+```
+
+### Blobs
+
+It is not always possible to create a transformation for the data that has to be
+added to a trace. For example, if the data is a result of a computation, and not
+a direct subset of another data stream..
+
+The following snippet shows how to create a new data stream of dataType `raw` on a trace from a blob stored in `bytes`:
+
+```python
+data = {'raw': b'...'}
+trace.update(data=data);
+```
+
+.. _python_snippets_data_streaming:
+
+#### Streaming data
+
+.. warning:: Streaming data does not work with the Hansken.py runner because Hansken.py does not support it. It does
+ work when running your plugin in Hansken and in the test framework.
+
+When dealing with large quantities of data, it is possible to keep the memory usage
+of the plugin within manageable limits by streaming the data from the plugin to Hansken in smaller chunks.
+To do this, use the `with trace.open(data_type=..., mode='wb')` syntax. Here are some examples:
+
+Stream strings to `raw` (default) datastream:
+
+```python
+with trace.open(mode='wb') as writer:
+ writer.write(b'a string')
+ writer.write(bytes(another_string, 'utf-8'))
+```
+
+Stream a BufferedReader object to a `text` datastream:
+
+```python
+with trace.open(data_type='text', mode='wb') as output, open('input.text', 'rb') as in_file:
+ output.write(in_file)
+```
+
+#### Streaming text
+
+To write `str` values directly, use mode `w` (or `wt`).
+By default, it is assumed that the written text is 'utf-8' encoded. The default encoding can be overwritten by using the `'encoding='` argument.
+
+(In a future Hansken update) Hansken will set the correct data-stream properties for your text stream (`mimeType`, `mimeClass`, and `fileType`).
+
+```python
+with trace.open(data_type='raw', mode='w', encoding='utf-8') as text_writer:
+ text_writer.write('hello.world') # write strings directly to the writer
+ json.dump({'hello': 'world'}, text_writer) # or pass the writer to json.dump
+```
+
+It is recommended to pass `utf-8` explictly as encoding.
+
+## Specifying system resources
+
+It is possible to specify system resources hints in the `PluginInfo`. To run a plugin with at least 0.5 cpu (= 0.5
+vCPU/Core/hyperthread), 1 gb memory and 10 (concurrent) cpu workers (threads), for example, the following configuration can be added to `PluginInfo`:
+
+```python
+plugin_info = PluginInfo(...,
+ resources=PluginResources(maximum_cpu=0.5, maximum_memory=1000, maximum_workers=10))
+```
+
+.. _python_snippets_deferred:
+
+## Deferred Plugins
+
+Implementing a deferred extraction plugin requires inheriting the
+:py:class:`DeferredExtractionPlugin `
+base class.
+
+```python
+class DeferredPlugin(DeferredExtractionPlugin):
+ def process(self, trace, context, searcher):
+```
+
+This allows accessing a third :py:class:`TraceSearcher `
+parameter in the process function. This can be used to search for traces:
+
+```python
+with searcher.search('file.extension:html', 10, scope='image') as searchresult:
+ for trace in searchresult:
+ log.debug(f'extension {trace.get("file.extension")}')
+```
+
+The ``search`` method accepts three arguments;
+1. a HQL query (note: this is the traditional HQL query, and not the matchers HQL-lite variant),
+2. (optional) the maximum number of traces to return (currently hard-limited to a maximum of 50 traces),
+3. (optional) a scope, which can be either `image`, or `project`. When set to `image`, the searcher will only search for traces
+ within the same image as the trace that is being processed.
+
+The returned :py:class:`SearchResult `
+should be closed, for example by using `with`. The resulting search result is an iterable, which will be exhausted when
+no more traces are available. The search result allows taking one or more traces by calling :py:
+meth:`take ` or
+:py:meth:`takeone `.
+
+.. note:: The command `trace.open(datastream_type)` will fail on search result traces that do not originate from the
+ same image (evidence item) as the trace that is being processed.
+
+## Deferred Meta Extraction Plugins
+
+Implementing a deferred meta extraction plugin requires inheriting the
+:py:class:`DeferredMetaExtractionPlugin `
+base class. This plugin is not able to call the trace.open() method since the actual trace data is not available to this plugin.
+Also matching on data type will not work for this plugin since this plugin only works for meta traces
+
+```python
+class DeferredMetaPlugin(DeferredMetaExtractionPlugin):
+ def plugin_info(self):
+ ...
+
+ def process(self, trace, searcher):
+ ...
+```
+
+## Bulk Mode
+
+The `PluginInfo` contains a parameter `bulk_mode`. This can be used for lightweight plugins which have to process a lot
+of data (either a lot of traces with data or a small number of traces with large data streams). For streaming
+extractions, these plugins will run inside the worker pod, and will therefore be able to process data more efficiently.
+
+**WARNING**: The plugin should be lightweight. This means that it should not use a lot of resources like CPU or memory,
+because this will limit the resources of the worker pod, and therefore Hansken will not be able to start enough workers
+to do extractions.
+
+Creating a plugin with bulk mode enabled can be done by setting the parameter to `True` in the `PluginInfo` as follows:
+
+```python
+plugin_info = PluginInfo(...,
+ bulk_mode=True)
+```
+
+## Logging
+
+We use Logbook to log messages in Python. Logbook is a logging system for Python that replaces the standard library’s
+logging module.
+
+To enable logging in your plugin, add the following to the top of your plugin code:
+
+```python
+from logbook import Logger
+
+log = Logger(__name__)
+```
+
+From there on the logging is pretty straight forward:
+
+```python
+log.info(f'Logging a variable: {my_variable}')
+```
+
+The default log level is `WARNING`. There are two ways to set the logging level. You can use the `-v` (or `-vv` or `-vvv`) option of `serve_plugin.py` to increase the log level. This is typically done in the plugin `Dockerfile`. Another option is to use an environment variable, `LOG_LEVEL`. Available levels are `WARNING`, `NOTICE`, `INFO` and `DEBUG`. The environment variable overrides the option.
+
+.. warning:: Be careful with logging sensitive information.
+
+.. note:: Contact your Hansken administrator for more information on where to find logs for your Hansken environment.
+
+## [EXPERIMENTAL FEATURE] Adding previews to a trace
+
+.. warning:: This is an experimental feature, which might change or get removed in future releases.
+
+Use :py:meth:`update `
+to add previews to an
+:py:class:`ExtractionTrace `.
+Example:
+
+```python
+def process(self, trace, data_context):
+ # set the preview data for the image/png MIME-type
+ trace.update('preview.image/png', b'\x00\xff')
+```
diff --git a/0.9.16/_sources/dev/python/testing.md.txt b/0.9.16/_sources/dev/python/testing.md.txt
new file mode 100644
index 0000000..a198ffd
--- /dev/null
+++ b/0.9.16/_sources/dev/python/testing.md.txt
@@ -0,0 +1,134 @@
+# Advanced use of the Test Framework in Python
+
+.. _python testing:
+
+This section assumes you use the same setup as is used in
+the [Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples).
+
+By default, the build scripts as described in the [Getting Started](getting_started.md) section will automatically run
+tests. The appropriate commands have been added to the tox.ini directly. This section gives a little more detail on the
+test commands and options.
+
+One can simply create unit tests for a plugin directly. However, we also provide a test-framework for testing them over
+gRPC. The test-framework serves a running instance of a Python plugin, and feeds it input files and compares the results
+against an expected result set.
+
+Note that the test-framework is implemented in Java, hence the Java 11 requirement. A jar file is included in the Python
+SDK which is called from a Python wrapper.
+
+## Regenerate expected test results
+
+The build and test scripts run some integration tests. To update the expected test outcome, the following command can be
+used:
+
+```bash
+tox -e regenerate
+```
+
+## Standalone testing
+
+The test runner is a script called `test_plugin` which is available in the SDK.
+
+To get started, `cd` into the directory of the plugin you want to test and run:
+
+```bash
+test_plugin --standalone plugin/chat_plugin.py
+```
+
+Note that the argument provided to the option `--standalone` in this example is the relative path to the plugin `py`
+file which is to be tested. This test accepts input files from the directory `testdata/input` and compares the results
+to the result files in found in `testdata/results`. Use the optional argument `--regenerate` to regenerate the expected
+results for the test when needed.
+
+This standalone test is also used by the `tox.ini` file to validate the plugin. Simply calling `tox` should be enough to
+install all dependencies and run the test.
+
+In case the plugin was defined in a module that is to be imported by the interpreter, it's also possible to run this
+command by referencing either the module that defines the plugin or the plugin directly:
+
+```bash
+test_plugin --standalone plugin_module
+# or, assuming the plugin class is called ChatPlugin
+test_plugin --standalone plugin_module:ChatPlugin
+```
+
+## Testing with a Docker image
+
+If there is a docker image available for the plugin you can also test it by executing:
+
+```bash
+test_plugin --docker extraction-plugin-examples-my-plugin
+```
+
+Replace the 'extraction-plugin-examples-chat' with the docker image you want to test. Run the following command to see
+which docker images are available:
+
+```bash
+docker images
+```
+
+## Manual testing
+
+The third option for testing is a manually started plugin. Start the plugin service in a terminal by executing:
+
+```bash
+serve_plugin -vvv plugin/my_plugin.py
+```
+
+This will spin up the chat plugin at port 8999. In this example the argument is also a path to the plugin's `.py` file.
+The same alternative reference to the plugin shown with `test_plugin` above applies here.
+
+In another terminal window, run the test with:
+
+```bash
+test_plugin --manual localhost 8999
+```
+
+## Tip: Start tests in your IDE
+
+To start the extraction plugin from code, create a `__main__` method which calls the `_test_validate_standalone`
+method of the test framework (see the example below). This method causes the extraction plugin to be started and
+supplied with data by the FLITS test framework. In this way the test can be started from the IDE, which has the
+advantage that it is easier to debug.
+
+```python
+from hansken_extraction_plugin.test_framework.test_plugin import _test_validate_standalone
+from hansken_extraction_plugin.api.extraction_plugin import ExtractionPlugin
+
+
+class PluginToTest(ExtractionPlugin):
+
+ def plugin_info(self):
+ # return plugin info
+ pass
+
+ def process(self, trace, data_context):
+ # process the data/trace here
+ pass
+
+
+if __name__ == '__main__':
+ _test_validate_standalone(PluginToTest, 'testdata/input', 'testdata/result', False)
+```
+
+## Help
+
+Run the following for an overview of all the available options in the test script:
+
+```bash
+test_plugin --help
+```
+
+## Testing transformers
+
+The sdk includes a script to test transformer functions. This script will start an extraction plugin, executes a single transformer function with the provided arguments and stops the plugin. Try it with:
+
+```bash
+execute_transformer docker_image method_name arguments
+```
+
+The arguments are provided in named argument pairs, so calling a method called `translate` with two parameters `language` and `input` might look like this:
+
+```bash
+execute_transformer translator_plugin translate language nl_NL input "example input"
+```
diff --git a/0.9.16/_sources/dev/python/transformers.md.txt b/0.9.16/_sources/dev/python/transformers.md.txt
new file mode 100644
index 0000000..c0f6bfe
--- /dev/null
+++ b/0.9.16/_sources/dev/python/transformers.md.txt
@@ -0,0 +1,100 @@
+# Using Transformers for on-demand execution
+
+## What are Transformers?
+
+Transformers are methods inside a plugin that can be called remotely at any moment.
+This allows for live plugin execution independent of extraction.
+Examples on how transformers could be used:
+- For searching images using text (i.e. a purple car).
+- For translating text in traces so that an investigator can read the text in their preferred language in a UI.
+- For converting speech to text.
+
+## How do Transformers work in Hansken?
+
+Transformers can be implemented in extraction plugins. Using the Hansken REST API, calls can be made to a
+specific plugin's transformer by specifying the transformer one wishes to call as well as its arguments.
+Hansken can automatically discover transformers before plugins are actually started.
+Once it has received a request for invoking a transformer it can choose to start the plugin's Docker container if it
+is not already started and send the request once it is up and running.
+
+## Developing Transformers
+
+This section assumes you use the same setup as is used in the [Extraction Plugin Examples](https://github.com/NetherlandsForensicInstitute/hansken-extraction-plugin-template-python/).
+
+A transformer can be easily defined by using the transformer decorator. By creating a method inside your plugin and
+decorating it with the transformer decorator it will automatically be made available to be remotely called.
+
+```python
+from hansken_extraction_plugin.api.extraction_plugin import ExtractionPlugin
+from hansken_extraction_plugin.decorators.transformer import transformer
+class Plugin(ExtractionPlugin):
+
+ def plugin_info(self):
+ ...
+
+ def process(self, trace, data_context):
+ ...
+
+ @transformer
+ def translate_text(self, text: str, language: str) -> str:
+ # To implement: Translate the text here.
+ return "Translated text"
+
+```
+
+## Limitations on Transformers
+
+However, there are some limitations on which methods can be turned into a decorator method:
+
+- Transformers may only be defined on methods of a class that derives (indirectly) from BaseExtractionPlugin.
+ - Note: ExtractionPlugin derives from BaseExtractionPlugin and is therefore allowed.
+- Transformer may not be static methods.
+- All parameters and the return type must be annotated with type hints (except the `self` parameter).
+- Parameters may not be positional-only or contain variable parameters like `*args` or `**kwarg`.
+- Parameters and return types may only be of the following (returning None is not supported):
+ - bool
+ - int
+ - float
+ - str
+ - bytes
+ - bytearray
+ - datetime.datetime (The datetime will be converted to unix time and then converted to datetime with the zone "UTC". So the zone id is always("UTC"))
+ - hansken.util.GeographicLocation
+ - hansken.util.Vector
+ - typing.Sequence
+ - typing.Mapping
+
+Upon starting a plugin every method decorated with the transformer decorator will be automatically validated to see if they adhere to these requirements. An exception will be thrown when a method does not adhere to all of these requirements.
+
+## Error handling in Transformers
+
+If at some point a transformer wishes to signal to the caller of the transformer that something has gone wrong
+it can simply throw a suitable exception. Any exceptions being thrown will automatically be propagated to the client
+calling the transformer by wrapping the exception in a gRPC exception. The stack trace will be provided as well for
+debugging purposes.
+
+## Testing transformers
+
+For more information on testing transformers, see the 'Testing Transformers' section on the [page on testing plugins](testing.md).
+
+## Calling transformer with /tools/transformers
+
+This REST Call calls a specified transformer method on a given tool/plugin with supplied arguments.
+This endpoint allows a transformer to be executed on demand, outside an extraction. The transformer call can be used to transform user input to an output. A tool/plugin can have multiple transformer methods.
+To use /tools/transformers REST CALL you need to know the following (see extractions tool or use the rest call/tools to get the information below):
+- The name of the plugin/tool
+- The transformer name
+- The names of the parameters (arguments)
+
+Example JSON request body for the transformer above:
+
+```JSON
+{
+ "arguments": {
+ "text": "value1",
+ "language": "value2"
+ },
+ "tool": "ExampleTool",
+ "transformer": "translate_text"
+}
+```
diff --git a/0.9.16/_sources/dev/spec.md.txt b/0.9.16/_sources/dev/spec.md.txt
new file mode 100644
index 0000000..2c23ddb
--- /dev/null
+++ b/0.9.16/_sources/dev/spec.md.txt
@@ -0,0 +1,67 @@
+# Extraction Plugin specifications
+
+.. note:: If you use the Java or Python extraction plugin SDK, you don't have
+ to worry about these specifications. The Java and Python SDKs makes
+ sure your plugin is compiled and packaged conform to the extraction
+ plugin specifications.
+
+This page describes the specifications that define an extraction plugin.
+The spec contains two major parts: a plugin protocol, and the plugin packaging
+method.
+
+This specification applies to plugins that are not embedded within Hansken,
+but to plugins that developed and distributed outside the scope of the Hansken
+platform development.
+
+## Plugin protocol
+
+An extraction plugin is a process that implements a [GRPC](https://grpc.io/)
+service `ExtractionPluginService`. The service defines a protocol that is
+used to allow communication between Hansken and an extraction plugin. The
+GRPC and protocol definitions can be found in the extraction plugin source
+code, under the folder `grpc`.
+
+.. note:: The source code of the Extraction Plugin is currently not available
+ outside the scope of the Hansken core development teams. If you are
+ interested in the GRPC definitions, please :doc:`../contact` the
+ Hansken development team.
+
+## Packaging
+
+An extraction plugin is packaged as a container image -- conform the open
+container initiative [image spec](https://github.com/opencontainers/image-spec).
+An extraction plugin can be
+
+The `ENTRYPOINT` of the container image should be a process that starts a GRPC
+server that implements the plugin protocol. The GRPC protocol should run on port
+`8999` by default. The plugin should always observe the `PLUGIN_PORT` environment
+variable, and run on that port if set.
+
+The container image should be labeled with the plugin info.
+The plugin info returned by the plugin plugin-info call and container labels
+are required to match. If not, Hansken will not accept your plugin during
+extractions - as it is unsure if the intended plugin is processing traces.
+
+The labels that are expected are:
+
+* `org.hansken.plugin-info.id` (conform :doc:`concepts/plugin_naming_convention`)
+* `org.hansken.plugin-info.version`
+* `org.hansken.plugin-info.api-version`
+* `org.hansken.plugin-info.description`
+* `org.hansken.plugin-info.webpage`
+* `org.hansken.plugin-info.deferred-iterations` (optional, only has a meaning for deferred extraction plugins)
+* `org.hansken.plugin-info.matcher` (see :doc:`concepts/hql_lite`)
+* `org.hansken.plugin-info.license`
+* `org.hansken.plugin-info.author-name`
+* `org.hansken.plugin-info.author-organisation`
+* `org.hansken.plugin-info.author-email`
+* `org.hansken.plugin-info.resource-max_cpu` (in millicpu, optional)
+* `org.hansken.plugin-info.resource-max_mem` (in mbs, optional)
+* `org.hansken.plugin-info.bulk-mode` (optional, false by default)
+* `org.hansken.plugin-info.transformers` (the signatures of the transformer methods as JSON). The transformers field contains a JSON array and is structured as follows:
+
+ ```json
+ [
+ {"method_name": "test_func", "parameters": {"arg_1": "str", "arg_2": "str"}, "return_type": "vector"}
+ ]
+ ```
diff --git a/0.9.16/_sources/index.md.txt b/0.9.16/_sources/index.md.txt
new file mode 100644
index 0000000..102cfd7
--- /dev/null
+++ b/0.9.16/_sources/index.md.txt
@@ -0,0 +1,52 @@
+# Hansken extraction plugin SDK documentation for plugin developers
+
+.. image:: cartoon.png
+ :width: 500
+
+## Quick links
+
+* [Latest version of this documentation](https://netherlandsforensicinstitute.github.io/hansken-extraction-plugin-sdk-documentation/latest)
+
+* Python:
+ [API Changelog](dev/python/api_changelog.md) |
+ [Code snippets](dev/python/snippets.md) |
+ [Python Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples/-/tree/master/python)
+
+* Java:
+ [API Changelog](dev/java/api_changelog.md) |
+ [Code snippets](dev/java/snippets.md) |
+ [Java Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples/-/tree/master/java)
+
+* [Extraction Plugins created by other community members](https://git.eminjenv.nl/hanskaton/extraction-plugins)
+* Chat with us on [Discord](https://discord.com/channels/785509947416772649/796720708096360478)
+
+N.B. For access to any of the above external resources, please see ":ref:`communityaccess`" the in the [FAQ](dev/faq.md).
+
+## Welcome
+
+Welcome to the Hansken extraction plugin SDK documentation for plugin developers.
+This documentation describes the Hansken extraction plugin Software Development Kit (SDK).
+If you are new here, you can start by reading the [introduction](dev/introduction).
+
+.. ATTENTION::
+ Hansken extraction plugins is a technology preview.
+ Please don't consider the SDK and integration in Hansken to be fully stable.
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Contents:
+
+ dev/introduction
+ dev/concepts
+ dev/spec
+ dev/java
+ dev/python
+ dev/examples
+ dev/faq
+ contact
+ changes
+
+
+
+Cartoon by @jorgb, all rights reserved
+
diff --git a/0.9.16/_static/_sphinx_javascript_frameworks_compat.js b/0.9.16/_static/_sphinx_javascript_frameworks_compat.js
new file mode 100644
index 0000000..8141580
--- /dev/null
+++ b/0.9.16/_static/_sphinx_javascript_frameworks_compat.js
@@ -0,0 +1,123 @@
+/* Compatability shim for jQuery and underscores.js.
+ *
+ * Copyright Sphinx contributors
+ * Released under the two clause BSD licence
+ */
+
+/**
+ * small helper function to urldecode strings
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
+ */
+jQuery.urldecode = function(x) {
+ if (!x) {
+ return x
+ }
+ return decodeURIComponent(x.replace(/\+/g, ' '));
+};
+
+/**
+ * small helper function to urlencode strings
+ */
+jQuery.urlencode = encodeURIComponent;
+
+/**
+ * This function returns the parsed url parameters of the
+ * current request. Multiple values per key are supported,
+ * it will always return arrays of strings for the value parts.
+ */
+jQuery.getQueryParameters = function(s) {
+ if (typeof s === 'undefined')
+ s = document.location.search;
+ var parts = s.substr(s.indexOf('?') + 1).split('&');
+ var result = {};
+ for (var i = 0; i < parts.length; i++) {
+ var tmp = parts[i].split('=', 2);
+ var key = jQuery.urldecode(tmp[0]);
+ var value = jQuery.urldecode(tmp[1]);
+ if (key in result)
+ result[key].push(value);
+ else
+ result[key] = [value];
+ }
+ return result;
+};
+
+/**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+jQuery.fn.highlightText = function(text, className) {
+ function highlight(node, addItems) {
+ if (node.nodeType === 3) {
+ var val = node.nodeValue;
+ var pos = val.toLowerCase().indexOf(text);
+ if (pos >= 0 &&
+ !jQuery(node.parentNode).hasClass(className) &&
+ !jQuery(node.parentNode).hasClass("nohighlight")) {
+ var span;
+ var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.className = className;
+ }
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ node.parentNode.insertBefore(span, node.parentNode.insertBefore(
+ document.createTextNode(val.substr(pos + text.length)),
+ node.nextSibling));
+ node.nodeValue = val.substr(0, pos);
+ if (isInSVG) {
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ var bbox = node.parentElement.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute('class', className);
+ addItems.push({
+ "parent": node.parentNode,
+ "target": rect});
+ }
+ }
+ }
+ else if (!jQuery(node).is("button, select, textarea")) {
+ jQuery.each(node.childNodes, function() {
+ highlight(this, addItems);
+ });
+ }
+ }
+ var addItems = [];
+ var result = this.each(function() {
+ highlight(this, addItems);
+ });
+ for (var i = 0; i < addItems.length; ++i) {
+ jQuery(addItems[i].parent).before(addItems[i].target);
+ }
+ return result;
+};
+
+/*
+ * backward compatibility for jQuery.browser
+ * This will be supported until firefox bug is fixed.
+ */
+if (!jQuery.browser) {
+ jQuery.uaMatch = function(ua) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+ /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+ /(msie) ([\w.]+)/.exec(ua) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+ };
+ jQuery.browser = {};
+ jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
+}
diff --git a/0.9.16/_static/basic.css b/0.9.16/_static/basic.css
new file mode 100644
index 0000000..f316efc
--- /dev/null
+++ b/0.9.16/_static/basic.css
@@ -0,0 +1,925 @@
+/*
+ * basic.css
+ * ~~~~~~~~~
+ *
+ * Sphinx stylesheet -- basic theme.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+div.section::after {
+ display: block;
+ content: '';
+ clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+ word-wrap: break-word;
+ overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+ overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+ float: left;
+ width: 80%;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+ float: left;
+ width: 20%;
+ border-left: none;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+
+img {
+ border: 0;
+ max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li p.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+ width: 100%;
+}
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+ padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+div.modindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+ padding: 2px;
+ border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+ min-width: 360px;
+ max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+
+a.headerlink {
+ visibility: hidden;
+}
+
+a:visited {
+ color: #551A8B;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+ clear: left;
+ float: left;
+ margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+ clear: right;
+ float: right;
+ margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-center {
+ text-align: center;
+}
+
+.align-default {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+ clear: right;
+ overflow-x: auto;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+nav.contents,
+aside.topic,
+div.admonition, div.topic, blockquote {
+ clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+nav.contents,
+aside.topic,
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+nav.contents > :last-child,
+aside.topic > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+ margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+nav.contents::after,
+aside.topic::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.align-default {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table caption span.caption-number {
+ font-style: italic;
+}
+
+table caption span.caption-text {
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 5px;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px;
+}
+
+table.citation td {
+ border-bottom: none;
+}
+
+th > :first-child,
+td > :first-child {
+ margin-top: 0px;
+}
+
+th > :last-child,
+td > :last-child {
+ margin-bottom: 0px;
+}
+
+/* -- figures --------------------------------------------------------------- */
+
+div.figure, figure {
+ margin: 0.5em;
+ padding: 0.5em;
+}
+
+div.figure p.caption, figcaption {
+ padding: 0.3em;
+}
+
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
+ font-style: italic;
+}
+
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
+}
+
+/* -- field list styles ----------------------------------------------------- */
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.field-name {
+ -moz-hyphens: manual;
+ -ms-hyphens: manual;
+ -webkit-hyphens: manual;
+ hyphens: manual;
+}
+
+/* -- hlist styles ---------------------------------------------------------- */
+
+table.hlist {
+ margin: 1em 0;
+}
+
+table.hlist td {
+ vertical-align: top;
+}
+
+/* -- object description styles --------------------------------------------- */
+
+.sig {
+ font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+}
+
+.sig-name, code.descname {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.sig-name {
+ font-size: 1.1em;
+}
+
+code.descname {
+ font-size: 1.2em;
+}
+
+.sig-prename, code.descclassname {
+ background-color: transparent;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.sig-paren {
+ font-size: larger;
+}
+
+.sig-param.n {
+ font-style: italic;
+}
+
+/* C++ specific styling */
+
+.sig-inline.c-texpr,
+.sig-inline.cpp-texpr {
+ font-family: unset;
+}
+
+.sig.c .k, .sig.c .kt,
+.sig.cpp .k, .sig.cpp .kt {
+ color: #0033B3;
+}
+
+.sig.c .m,
+.sig.cpp .m {
+ color: #1750EB;
+}
+
+.sig.c .s, .sig.c .sc,
+.sig.cpp .s, .sig.cpp .sc {
+ color: #067D17;
+}
+
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+ list-style: decimal;
+}
+
+ol.loweralpha {
+ list-style: lower-alpha;
+}
+
+ol.upperalpha {
+ list-style: upper-alpha;
+}
+
+ol.lowerroman {
+ list-style: lower-roman;
+}
+
+ol.upperroman {
+ list-style: upper-roman;
+}
+
+:not(li) > ol > li:first-child > :first-child,
+:not(li) > ul > li:first-child > :first-child {
+ margin-top: 0px;
+}
+
+:not(li) > ol > li:last-child > :last-child,
+:not(li) > ul > li:last-child > :last-child {
+ margin-bottom: 0px;
+}
+
+ol.simple ol p,
+ol.simple ul p,
+ul.simple ol p,
+ul.simple ul p {
+ margin-top: 0;
+}
+
+ol.simple > li:not(:first-child) > p,
+ul.simple > li:not(:first-child) > p {
+ margin-top: 0;
+}
+
+ol.simple p,
+ul.simple p {
+ margin-bottom: 0;
+}
+
+aside.footnote > span,
+div.citation > span {
+ float: left;
+}
+aside.footnote > span:last-of-type,
+div.citation > span:last-of-type {
+ padding-right: 0.5em;
+}
+aside.footnote > p {
+ margin-left: 2em;
+}
+div.citation > p {
+ margin-left: 4em;
+}
+aside.footnote > p:last-of-type,
+div.citation > p:last-of-type {
+ margin-bottom: 0em;
+}
+aside.footnote > p:last-of-type:after,
+div.citation > p:last-of-type:after {
+ content: "";
+ clear: both;
+}
+
+dl.field-list {
+ display: grid;
+ grid-template-columns: fit-content(30%) auto;
+}
+
+dl.field-list > dt {
+ font-weight: bold;
+ word-break: break-word;
+ padding-left: 0.5em;
+ padding-right: 5px;
+}
+
+dl.field-list > dd {
+ padding-left: 0.5em;
+ margin-top: 0em;
+ margin-left: 0em;
+ margin-bottom: 0em;
+}
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd > :first-child {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+.sig dd {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.sig dl {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+dl > dd:last-child,
+dl > dd:last-child > :last-child {
+ margin-bottom: 0;
+}
+
+dt:target, span.highlighted {
+ background-color: #fbe54e;
+}
+
+rect.highlighted {
+ fill: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa;
+}
+
+.line-block {
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.line-block .line-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+ font-family: sans-serif;
+}
+
+.accelerator {
+ text-decoration: underline;
+}
+
+.classifier {
+ font-style: oblique;
+}
+
+.classifier:before {
+ font-style: normal;
+ margin: 0 0.5em;
+ content: ":";
+ display: inline-block;
+}
+
+abbr, acronym {
+ border-bottom: dotted 1px;
+ cursor: help;
+}
+
+.translated {
+ background-color: rgba(207, 255, 207, 0.2)
+}
+
+.untranslated {
+ background-color: rgba(255, 207, 207, 0.2)
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+ overflow-y: hidden; /* fixes display issues on Chrome browsers */
+}
+
+pre, div[class*="highlight-"] {
+ clear: both;
+}
+
+span.pre {
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ -webkit-hyphens: none;
+ hyphens: none;
+ white-space: nowrap;
+}
+
+div[class*="highlight-"] {
+ margin: 1em 0;
+}
+
+td.linenos pre {
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ display: block;
+}
+
+table.highlighttable tbody {
+ display: block;
+}
+
+table.highlighttable tr {
+ display: flex;
+}
+
+table.highlighttable td {
+ margin: 0;
+ padding: 0;
+}
+
+table.highlighttable td.linenos {
+ padding-right: 0.5em;
+}
+
+table.highlighttable td.code {
+ flex: 1;
+ overflow: hidden;
+}
+
+.highlight .hll {
+ display: block;
+}
+
+div.highlight pre,
+table.highlighttable pre {
+ margin: 0;
+}
+
+div.code-block-caption + div {
+ margin-top: 0;
+}
+
+div.code-block-caption {
+ margin-top: 1em;
+ padding: 2px 5px;
+ font-size: small;
+}
+
+div.code-block-caption code {
+ background-color: transparent;
+}
+
+table.highlighttable td.linenos,
+span.linenos,
+div.highlight span.gp { /* gp: Generic.Prompt */
+ user-select: none;
+ -webkit-user-select: text; /* Safari fallback only */
+ -webkit-user-select: none; /* Chrome/Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+ */
+}
+
+div.code-block-caption span.caption-number {
+ padding: 0.1em 0.3em;
+ font-style: italic;
+}
+
+div.code-block-caption span.caption-text {
+}
+
+div.literal-block-wrapper {
+ margin: 1em 0;
+}
+
+code.xref, a code {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+ background-color: transparent;
+}
+
+.viewcode-link {
+ float: right;
+}
+
+.viewcode-back {
+ float: right;
+ font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+ margin: -1px -10px;
+ padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.body div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+span.eqno a.headerlink {
+ position: absolute;
+ z-index: 1;
+}
+
+div.math:hover a.headerlink {
+ visibility: visible;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0 !important;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/0.9.16/_static/css/badge_only.css b/0.9.16/_static/css/badge_only.css
new file mode 100644
index 0000000..88ba55b
--- /dev/null
+++ b/0.9.16/_static/css/badge_only.css
@@ -0,0 +1 @@
+.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}
\ No newline at end of file
diff --git a/0.9.16/_static/css/fonts/Roboto-Slab-Bold.woff b/0.9.16/_static/css/fonts/Roboto-Slab-Bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/0.9.16/_static/css/fonts/Roboto-Slab-Bold.woff differ
diff --git a/0.9.16/_static/css/fonts/Roboto-Slab-Bold.woff2 b/0.9.16/_static/css/fonts/Roboto-Slab-Bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/0.9.16/_static/css/fonts/Roboto-Slab-Bold.woff2 differ
diff --git a/0.9.16/_static/css/fonts/Roboto-Slab-Regular.woff b/0.9.16/_static/css/fonts/Roboto-Slab-Regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/0.9.16/_static/css/fonts/Roboto-Slab-Regular.woff differ
diff --git a/0.9.16/_static/css/fonts/Roboto-Slab-Regular.woff2 b/0.9.16/_static/css/fonts/Roboto-Slab-Regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/0.9.16/_static/css/fonts/Roboto-Slab-Regular.woff2 differ
diff --git a/0.9.16/_static/css/fonts/fontawesome-webfont.eot b/0.9.16/_static/css/fonts/fontawesome-webfont.eot
new file mode 100644
index 0000000..e9f60ca
Binary files /dev/null and b/0.9.16/_static/css/fonts/fontawesome-webfont.eot differ
diff --git a/0.9.16/_static/css/fonts/fontawesome-webfont.svg b/0.9.16/_static/css/fonts/fontawesome-webfont.svg
new file mode 100644
index 0000000..855c845
--- /dev/null
+++ b/0.9.16/_static/css/fonts/fontawesome-webfont.svg
@@ -0,0 +1,2671 @@
+
+
+
diff --git a/0.9.16/_static/css/fonts/fontawesome-webfont.ttf b/0.9.16/_static/css/fonts/fontawesome-webfont.ttf
new file mode 100644
index 0000000..35acda2
Binary files /dev/null and b/0.9.16/_static/css/fonts/fontawesome-webfont.ttf differ
diff --git a/0.9.16/_static/css/fonts/fontawesome-webfont.woff b/0.9.16/_static/css/fonts/fontawesome-webfont.woff
new file mode 100644
index 0000000..400014a
Binary files /dev/null and b/0.9.16/_static/css/fonts/fontawesome-webfont.woff differ
diff --git a/0.9.16/_static/css/fonts/fontawesome-webfont.woff2 b/0.9.16/_static/css/fonts/fontawesome-webfont.woff2
new file mode 100644
index 0000000..4d13fc6
Binary files /dev/null and b/0.9.16/_static/css/fonts/fontawesome-webfont.woff2 differ
diff --git a/0.9.16/_static/css/fonts/lato-bold-italic.woff b/0.9.16/_static/css/fonts/lato-bold-italic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-bold-italic.woff differ
diff --git a/0.9.16/_static/css/fonts/lato-bold-italic.woff2 b/0.9.16/_static/css/fonts/lato-bold-italic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-bold-italic.woff2 differ
diff --git a/0.9.16/_static/css/fonts/lato-bold.woff b/0.9.16/_static/css/fonts/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-bold.woff differ
diff --git a/0.9.16/_static/css/fonts/lato-bold.woff2 b/0.9.16/_static/css/fonts/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-bold.woff2 differ
diff --git a/0.9.16/_static/css/fonts/lato-normal-italic.woff b/0.9.16/_static/css/fonts/lato-normal-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-normal-italic.woff differ
diff --git a/0.9.16/_static/css/fonts/lato-normal-italic.woff2 b/0.9.16/_static/css/fonts/lato-normal-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-normal-italic.woff2 differ
diff --git a/0.9.16/_static/css/fonts/lato-normal.woff b/0.9.16/_static/css/fonts/lato-normal.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-normal.woff differ
diff --git a/0.9.16/_static/css/fonts/lato-normal.woff2 b/0.9.16/_static/css/fonts/lato-normal.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/0.9.16/_static/css/fonts/lato-normal.woff2 differ
diff --git a/0.9.16/_static/css/theme.css b/0.9.16/_static/css/theme.css
new file mode 100644
index 0000000..0f14f10
--- /dev/null
+++ b/0.9.16/_static/css/theme.css
@@ -0,0 +1,4 @@
+html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*!
+ * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block}
\ No newline at end of file
diff --git a/0.9.16/_static/doctools.js b/0.9.16/_static/doctools.js
new file mode 100644
index 0000000..4d67807
--- /dev/null
+++ b/0.9.16/_static/doctools.js
@@ -0,0 +1,156 @@
+/*
+ * doctools.js
+ * ~~~~~~~~~~~
+ *
+ * Base JavaScript utilities for all Sphinx HTML documentation.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
+ "TEXTAREA",
+ "INPUT",
+ "SELECT",
+ "BUTTON",
+]);
+
+const _ready = (callback) => {
+ if (document.readyState !== "loading") {
+ callback();
+ } else {
+ document.addEventListener("DOMContentLoaded", callback);
+ }
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const Documentation = {
+ init: () => {
+ Documentation.initDomainIndexTable();
+ Documentation.initOnKeyListeners();
+ },
+
+ /**
+ * i18n support
+ */
+ TRANSLATIONS: {},
+ PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
+ LOCALE: "unknown",
+
+ // gettext and ngettext don't access this so that the functions
+ // can safely bound to a different name (_ = Documentation.gettext)
+ gettext: (string) => {
+ const translated = Documentation.TRANSLATIONS[string];
+ switch (typeof translated) {
+ case "undefined":
+ return string; // no translation
+ case "string":
+ return translated; // translation exists
+ default:
+ return translated[0]; // (singular, plural) translation tuple exists
+ }
+ },
+
+ ngettext: (singular, plural, n) => {
+ const translated = Documentation.TRANSLATIONS[singular];
+ if (typeof translated !== "undefined")
+ return translated[Documentation.PLURAL_EXPR(n)];
+ return n === 1 ? singular : plural;
+ },
+
+ addTranslations: (catalog) => {
+ Object.assign(Documentation.TRANSLATIONS, catalog.messages);
+ Documentation.PLURAL_EXPR = new Function(
+ "n",
+ `return (${catalog.plural_expr})`
+ );
+ Documentation.LOCALE = catalog.locale;
+ },
+
+ /**
+ * helper function to focus on search bar
+ */
+ focusSearchBar: () => {
+ document.querySelectorAll("input[name=q]")[0]?.focus();
+ },
+
+ /**
+ * Initialise the domain index toggle buttons
+ */
+ initDomainIndexTable: () => {
+ const toggler = (el) => {
+ const idNumber = el.id.substr(7);
+ const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
+ if (el.src.substr(-9) === "minus.png") {
+ el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
+ toggledRows.forEach((el) => (el.style.display = "none"));
+ } else {
+ el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
+ toggledRows.forEach((el) => (el.style.display = ""));
+ }
+ };
+
+ const togglerElements = document.querySelectorAll("img.toggler");
+ togglerElements.forEach((el) =>
+ el.addEventListener("click", (event) => toggler(event.currentTarget))
+ );
+ togglerElements.forEach((el) => (el.style.display = ""));
+ if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
+ },
+
+ initOnKeyListeners: () => {
+ // only install a listener if it is really needed
+ if (
+ !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
+ !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
+ )
+ return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
+
+ if (!event.shiftKey) {
+ switch (event.key) {
+ case "ArrowLeft":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const prevLink = document.querySelector('link[rel="prev"]');
+ if (prevLink && prevLink.href) {
+ window.location.href = prevLink.href;
+ event.preventDefault();
+ }
+ break;
+ case "ArrowRight":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const nextLink = document.querySelector('link[rel="next"]');
+ if (nextLink && nextLink.href) {
+ window.location.href = nextLink.href;
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+
+ // some keyboard layouts may need Shift to get /
+ switch (event.key) {
+ case "/":
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
+ Documentation.focusSearchBar();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+// quick alias for translations
+const _ = Documentation.gettext;
+
+_ready(Documentation.init);
diff --git a/0.9.16/_static/documentation_options.js b/0.9.16/_static/documentation_options.js
new file mode 100644
index 0000000..c83c919
--- /dev/null
+++ b/0.9.16/_static/documentation_options.js
@@ -0,0 +1,13 @@
+const DOCUMENTATION_OPTIONS = {
+ VERSION: '0.9.16',
+ LANGUAGE: 'en',
+ COLLAPSE_INDEX: false,
+ BUILDER: 'html',
+ FILE_SUFFIX: '.html',
+ LINK_SUFFIX: '.html',
+ HAS_SOURCE: true,
+ SOURCELINK_SUFFIX: '.txt',
+ NAVIGATION_WITH_KEYS: false,
+ SHOW_SEARCH_SUMMARY: true,
+ ENABLE_SEARCH_SHORTCUTS: true,
+};
\ No newline at end of file
diff --git a/0.9.16/_static/file.png b/0.9.16/_static/file.png
new file mode 100644
index 0000000..a858a41
Binary files /dev/null and b/0.9.16/_static/file.png differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bold.eot b/0.9.16/_static/fonts/Lato/lato-bold.eot
new file mode 100644
index 0000000..3361183
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bold.eot differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bold.ttf b/0.9.16/_static/fonts/Lato/lato-bold.ttf
new file mode 100644
index 0000000..29f691d
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bold.ttf differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bold.woff b/0.9.16/_static/fonts/Lato/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bold.woff differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bold.woff2 b/0.9.16/_static/fonts/Lato/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bold.woff2 differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bolditalic.eot b/0.9.16/_static/fonts/Lato/lato-bolditalic.eot
new file mode 100644
index 0000000..3d41549
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bolditalic.eot differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bolditalic.ttf b/0.9.16/_static/fonts/Lato/lato-bolditalic.ttf
new file mode 100644
index 0000000..f402040
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bolditalic.ttf differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bolditalic.woff b/0.9.16/_static/fonts/Lato/lato-bolditalic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bolditalic.woff differ
diff --git a/0.9.16/_static/fonts/Lato/lato-bolditalic.woff2 b/0.9.16/_static/fonts/Lato/lato-bolditalic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-bolditalic.woff2 differ
diff --git a/0.9.16/_static/fonts/Lato/lato-italic.eot b/0.9.16/_static/fonts/Lato/lato-italic.eot
new file mode 100644
index 0000000..3f82642
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-italic.eot differ
diff --git a/0.9.16/_static/fonts/Lato/lato-italic.ttf b/0.9.16/_static/fonts/Lato/lato-italic.ttf
new file mode 100644
index 0000000..b4bfc9b
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-italic.ttf differ
diff --git a/0.9.16/_static/fonts/Lato/lato-italic.woff b/0.9.16/_static/fonts/Lato/lato-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-italic.woff differ
diff --git a/0.9.16/_static/fonts/Lato/lato-italic.woff2 b/0.9.16/_static/fonts/Lato/lato-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-italic.woff2 differ
diff --git a/0.9.16/_static/fonts/Lato/lato-regular.eot b/0.9.16/_static/fonts/Lato/lato-regular.eot
new file mode 100644
index 0000000..11e3f2a
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-regular.eot differ
diff --git a/0.9.16/_static/fonts/Lato/lato-regular.ttf b/0.9.16/_static/fonts/Lato/lato-regular.ttf
new file mode 100644
index 0000000..74decd9
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-regular.ttf differ
diff --git a/0.9.16/_static/fonts/Lato/lato-regular.woff b/0.9.16/_static/fonts/Lato/lato-regular.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-regular.woff differ
diff --git a/0.9.16/_static/fonts/Lato/lato-regular.woff2 b/0.9.16/_static/fonts/Lato/lato-regular.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/0.9.16/_static/fonts/Lato/lato-regular.woff2 differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot
new file mode 100644
index 0000000..79dc8ef
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf
new file mode 100644
index 0000000..df5d1df
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot
new file mode 100644
index 0000000..2f7ca78
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf
new file mode 100644
index 0000000..eb52a79
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ
diff --git a/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/0.9.16/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ
diff --git a/0.9.16/_static/javadoc/allclasses-index.html b/0.9.16/_static/javadoc/allclasses-index.html
new file mode 100644
index 0000000..1e3e682
--- /dev/null
+++ b/0.9.16/_static/javadoc/allclasses-index.html
@@ -0,0 +1,243 @@
+
+
+
+
+All Classes and Interfaces (Hansken Extraction Plugin SDK - API 0.9.16-master-188-037fa906 API)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Starting from the Overview page, you can browse the documentation using the links in each page, and in the navigation bar at the top of each page. The Index and Search box allow you to navigate to specific declarations and summary pages, including: All Packages, All Classes and Interfaces
+
+
Search
+
You can search for definitions of modules, packages, types, fields, methods, system properties and other terms defined in the API. These items can be searched using part or all of the name, optionally using "camelCase" abbreviations, or multiple search terms separated by whitespace. Some examples:
+The following sections describe the different kinds of pages in this collection.
+
+
Overview
+
The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.
+
+
+
Package
+
Each package has a page that contains a list of its classes and interfaces, with a summary for each. These pages may contain the following categories:
+
+
Interfaces
+
Classes
+
Enum Classes
+
Exception Classes
+
Annotation Interfaces
+
+
+
+
Class or Interface
+
Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a declaration and description, member summary tables, and detailed member descriptions. Entries in each of these sections are omitted if they are empty or not applicable.
+
+
Class Inheritance Diagram
+
Direct Subclasses
+
All Known Subinterfaces
+
All Known Implementing Classes
+
Class or Interface Declaration
+
Class or Interface Description
+
+
+
+
Nested Class Summary
+
Enum Constant Summary
+
Field Summary
+
Property Summary
+
Constructor Summary
+
Method Summary
+
Required Element Summary
+
Optional Element Summary
+
+
+
+
Enum Constant Details
+
Field Details
+
Property Details
+
Constructor Details
+
Method Details
+
Element Details
+
+
Note: Annotation interfaces have required and optional elements, but not methods. Only enum classes have enum constants. The components of a record class are displayed as part of the declaration of the record class. Properties are a feature of JavaFX.
+
The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
+
+
+
Other Files
+
Packages and modules may contain pages with additional information related to the declarations nearby.
+
+
+
Use
+
Each documented package, class and interface has its own Use page. This page describes what packages, classes, methods, constructors and fields use any part of the given class or package. Given a class or interface A, its Use page includes subclasses of A, fields declared as A, methods that return A, and methods and constructors with parameters of type A. You can access this page by first going to the package, class or interface, then clicking on the USE link in the navigation bar.
+
+
+
Tree (Class Hierarchy)
+
There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. Classes are organized by inheritance structure starting with java.lang.Object. Interfaces do not inherit from java.lang.Object.
+
+
When viewing the Overview page, clicking on TREE displays the hierarchy for all packages.
+
When viewing a particular package, class or interface page, clicking on TREE displays the hierarchy for only that package.
+
+
+
+
Deprecated API
+
The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to shortcomings, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
The All Packages page contains an alphabetic index of all packages contained in the documentation.
+
+
+
All Classes and Interfaces
+
The All Classes and Interfaces page contains an alphabetic index of all classes and interfaces contained in the documentation, including annotation interfaces, enum classes, and record classes.
+
+
+
Index
+
The Index contains an alphabetic index of all classes, interfaces, constructors, methods, and fields in the documentation, as well as summary pages such as All Packages, All Classes and Interfaces.
+
+
+
+This help file applies to API documentation generated by the standard doclet.
+
+
Read from the data sequence, returning the read bytes as an array.The data will be read from the current
+ position and the amount of bytes read will equal count, unless the sequence contains
+ fewer remaining bytes.
This package provides util classes for the Extraction Plugin SDK API.
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_static/javadoc/legal/ADDITIONAL_LICENSE_INFO b/0.9.16/_static/javadoc/legal/ADDITIONAL_LICENSE_INFO
new file mode 100644
index 0000000..ff700cd
--- /dev/null
+++ b/0.9.16/_static/javadoc/legal/ADDITIONAL_LICENSE_INFO
@@ -0,0 +1,37 @@
+ ADDITIONAL INFORMATION ABOUT LICENSING
+
+Certain files distributed by Oracle America, Inc. and/or its affiliates are
+subject to the following clarification and special exception to the GPLv2,
+based on the GNU Project exception for its Classpath libraries, known as the
+GNU Classpath Exception.
+
+Note that Oracle includes multiple, independent programs in this software
+package. Some of those programs are provided under licenses deemed
+incompatible with the GPLv2 by the Free Software Foundation and others.
+For example, the package includes programs licensed under the Apache
+License, Version 2.0 and may include FreeType. Such programs are licensed
+to you under their original licenses.
+
+Oracle facilitates your further distribution of this package by adding the
+Classpath Exception to the necessary parts of its GPLv2 code, which permits
+you to use that code in combination with other independent modules not
+licensed under the GPLv2. However, note that this would not permit you to
+commingle code under an incompatible license with Oracle's GPLv2 licensed
+code by, for example, cutting and pasting such code into a file also
+containing Oracle's GPLv2 licensed code and then distributing the result.
+
+Additionally, if you were to remove the Classpath Exception from any of the
+files to which it applies and distribute the result, you would likely be
+required to license some or all of the other code in that distribution under
+the GPLv2 as well, and since the GPLv2 is incompatible with the license terms
+of some items included in the distribution by Oracle, removing the Classpath
+Exception could therefore effectively compromise your ability to further
+distribute the package.
+
+Failing to distribute notices associated with some files may also create
+unexpected legal consequences.
+
+Proceed with caution and we recommend that you obtain the advice of a lawyer
+skilled in open source matters before removing the Classpath Exception or
+making modifications to this package which may subsequently be redistributed
+and/or involve the use of third party software.
diff --git a/0.9.16/_static/javadoc/legal/ASSEMBLY_EXCEPTION b/0.9.16/_static/javadoc/legal/ASSEMBLY_EXCEPTION
new file mode 100644
index 0000000..4296666
--- /dev/null
+++ b/0.9.16/_static/javadoc/legal/ASSEMBLY_EXCEPTION
@@ -0,0 +1,27 @@
+
+OPENJDK ASSEMBLY EXCEPTION
+
+The OpenJDK source code made available by Oracle America, Inc. (Oracle) at
+openjdk.org ("OpenJDK Code") is distributed under the terms of the GNU
+General Public License version 2
+only ("GPL2"), with the following clarification and special exception.
+
+ Linking this OpenJDK Code statically or dynamically with other code
+ is making a combined work based on this library. Thus, the terms
+ and conditions of GPL2 cover the whole combination.
+
+ As a special exception, Oracle gives you permission to link this
+ OpenJDK Code with certain code licensed by Oracle as indicated at
+ https://openjdk.org/legal/exception-modules-2007-05-08.html
+ ("Designated Exception Modules") to produce an executable,
+ regardless of the license terms of the Designated Exception Modules,
+ and to copy and distribute the resulting executable under GPL2,
+ provided that the Designated Exception Modules continue to be
+ governed by the licenses under which they were offered by Oracle.
+
+As such, it allows licensees and sublicensees of Oracle's GPL2 OpenJDK Code
+to build an executable that includes those portions of necessary code that
+Oracle could not provide under GPL2 (or that Oracle has provided under GPL2
+with the Classpath exception). If you modify or add to the OpenJDK code,
+that new GPL2 code may still be combined with Designated Exception Modules
+if the new code is made subject to this exception by its copyright holder.
diff --git a/0.9.16/_static/javadoc/legal/LICENSE b/0.9.16/_static/javadoc/legal/LICENSE
new file mode 100644
index 0000000..8b400c7
--- /dev/null
+++ b/0.9.16/_static/javadoc/legal/LICENSE
@@ -0,0 +1,347 @@
+The GNU General Public License (GPL)
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to
+most of the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom to
+distribute copies of free software (and charge for this service if you wish),
+that you receive source code or can get it if you want it, that you can change
+the software or use pieces of it in new free programs; and that you know you
+can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for
+a fee, you must give the recipients all the rights that you have. You must
+make sure that they, too, receive or can get the source code. And you must
+show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program proprietary.
+To prevent this, we have made it clear that any patent must be licensed for
+everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or
+translated into another language. (Hereinafter, translation is included
+without limitation in the term "modification".) Each licensee is addressed as
+"you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is
+not restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this License
+and to the absence of any warranty; and give any other recipients of the
+Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may
+at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all of
+these conditions:
+
+ a) You must cause the modified files to carry prominent notices stating
+ that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in whole or
+ in part contains or is derived from the Program or any part thereof, to be
+ licensed as a whole at no charge to all third parties under the terms of
+ this License.
+
+ c) If the modified program normally reads commands interactively when run,
+ you must cause it, when started running for such interactive use in the
+ most ordinary way, to print or display an announcement including an
+ appropriate copyright notice and a notice that there is no warranty (or
+ else, saying that you provide a warranty) and that users may redistribute
+ the program under these conditions, and telling the user how to view a copy
+ of this License. (Exception: if the Program itself is interactive but does
+ not normally print such an announcement, your work based on the Program is
+ not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License, and
+its terms, do not apply to those sections when you distribute them as separate
+works. But when you distribute the same sections as part of a whole which is a
+work based on the Program, the distribution of the whole must be on the terms
+of this License, whose permissions for other licensees extend to the entire
+whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise the
+right to control the distribution of derivative or collective works based on
+the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and
+2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable source
+ code, which must be distributed under the terms of Sections 1 and 2 above
+ on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three years, to
+ give any third party, for a charge no more than your cost of physically
+ performing source distribution, a complete machine-readable copy of the
+ corresponding source code, to be distributed under the terms of Sections 1
+ and 2 above on a medium customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer to
+ distribute corresponding source code. (This alternative is allowed only
+ for noncommercial distribution and only if you received the program in
+ object code or executable form with such an offer, in accord with
+ Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all
+the source code for all modules it contains, plus any associated interface
+definition files, plus the scripts used to control compilation and installation
+of the executable. However, as a special exception, the source code
+distributed need not include anything that is normally distributed (in either
+source or binary form) with the major components (compiler, kernel, and so on)
+of the operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so
+long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not
+accept this License. Therefore, by modifying or distributing the Program (or
+any work based on the Program), you indicate your acceptance of this License to
+do so, and all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to
+copy, distribute or modify the Program subject to these terms and conditions.
+You may not impose any further restrictions on the recipients' exercise of the
+rights granted herein. You are not responsible for enforcing compliance by
+third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues), conditions
+are imposed on you (whether by court order, agreement or otherwise) that
+contradict the conditions of this License, they do not excuse you from the
+conditions of this License. If you cannot distribute so as to satisfy
+simultaneously your obligations under this License and any other pertinent
+obligations, then as a consequence you may not distribute the Program at all.
+For example, if a patent license would not permit royalty-free redistribution
+of the Program by all those who receive copies directly or indirectly through
+you, then the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or
+other property right claims or to contest validity of any such claims; this
+section has the sole purpose of protecting the integrity of the free software
+distribution system, which is implemented by public license practices. Many
+people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose that
+choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original
+copyright holder who places the Program under this License may add an explicit
+geographical distribution limitation excluding those countries, so that
+distribution is permitted only in or among countries not thus excluded. In
+such case, this License incorporates the limitation as if written in the body
+of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems
+or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any later
+version", you have the option of following the terms and conditions either of
+that version or of any later version published by the Free Software Foundation.
+If the Program does not specify a version number of this License, you may
+choose any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of
+all derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
+PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
+ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
+PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
+INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
+BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
+OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach
+them to the start of each source file to most effectively convey the exclusion
+of warranty; and each file should have at least the "copyright" line and a
+pointer to where the full notice is found.
+
+ One line to give the program's name and a brief idea of what it does.
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when it
+starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
+ with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free
+ software, and you are welcome to redistribute it under certain conditions;
+ type 'show c' for details.
+
+The hypothetical commands 'show w' and 'show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may be
+called something other than 'show w' and 'show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the program, if necessary. Here
+is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ 'Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ signature of Ty Coon, 1 April 1989
+
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General Public
+License instead of this License.
+
+
+"CLASSPATH" EXCEPTION TO THE GPL
+
+Certain source files distributed by Oracle America and/or its affiliates are
+subject to the following clarification and special exception to the GPL, but
+only where Oracle has expressly included in the particular source file's header
+the words "Oracle designates this particular file as subject to the "Classpath"
+exception as provided by Oracle in the LICENSE file that accompanied this code."
+
+ Linking this library statically or dynamically with other modules is making
+ a combined work based on this library. Thus, the terms and conditions of
+ the GNU General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent modules,
+ and to copy and distribute the resulting executable under terms of your
+ choice, provided that you also meet, for each linked independent module,
+ the terms and conditions of the license of that module. An independent
+ module is a module which is not derived from or based on this library. If
+ you modify this library, you may extend this exception to your version of
+ the library, but you are not obligated to do so. If you do not wish to do
+ so, delete this exception statement from your version.
diff --git a/0.9.16/_static/javadoc/legal/jquery.md b/0.9.16/_static/javadoc/legal/jquery.md
new file mode 100644
index 0000000..d468b31
--- /dev/null
+++ b/0.9.16/_static/javadoc/legal/jquery.md
@@ -0,0 +1,72 @@
+## jQuery v3.6.1
+
+### jQuery License
+```
+jQuery v 3.6.1
+Copyright OpenJS Foundation and other contributors, https://openjsf.org/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+******************************************
+
+The jQuery JavaScript Library v3.6.1 also includes Sizzle.js
+
+Sizzle.js includes the following license:
+
+Copyright JS Foundation and other contributors, https://js.foundation/
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/jquery/sizzle
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+All files located in the node_modules and external directories are
+externally maintained libraries used by this software which have their
+own licenses; we recommend you read them, as their terms may differ from
+the terms above.
+
+*********************
+
+```
diff --git a/0.9.16/_static/javadoc/legal/jqueryUI.md b/0.9.16/_static/javadoc/legal/jqueryUI.md
new file mode 100644
index 0000000..8bda9d7
--- /dev/null
+++ b/0.9.16/_static/javadoc/legal/jqueryUI.md
@@ -0,0 +1,49 @@
+## jQuery UI v1.13.2
+
+### jQuery UI License
+```
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/jquery/jquery-ui
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code contained within the demos directory.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+All files located in the node_modules and external directories are
+externally maintained libraries used by this software which have their
+own licenses; we recommend you read them, as their terms may differ from
+the terms above.
+
+```
diff --git a/0.9.16/_static/javadoc/link.svg b/0.9.16/_static/javadoc/link.svg
new file mode 100644
index 0000000..7ccc5ed
--- /dev/null
+++ b/0.9.16/_static/javadoc/link.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/0.9.16/_static/javadoc/member-search-index.js b/0.9.16/_static/javadoc/member-search-index.js
new file mode 100644
index 0000000..1e5f0a8
--- /dev/null
+++ b/0.9.16/_static/javadoc/member-search-index.js
@@ -0,0 +1 @@
+memberSearchIndex = [{"p":"org.hansken.plugin.extraction.util","c":"ThrowingConsumer","l":"accept(T)"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation.Builder","l":"add(DataRange...)","u":"add(org.hansken.plugin.extraction.api.transformations.DataRange...)"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation.Builder","l":"add(List)","u":"add(java.util.List)"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation.Builder","l":"addRange(long, long)","u":"addRange(long,long)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"addTracelet(String, Consumer)","u":"addTracelet(java.lang.String,java.util.function.Consumer)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"addTracelet(Trace.Tracelet)","u":"addTracelet(org.hansken.plugin.extraction.api.Trace.Tracelet)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"addType(String)","u":"addType(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"addValue(String, T)","u":"addValue(java.lang.String,T)"},{"p":"org.hansken.plugin.extraction.api","c":"TraceSearcher","l":"ALL_SEARCH_RESULTS"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotAllNull(String, Object...)","u":"argNotAllNull(java.lang.String,java.lang.Object...)"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotEmpty(String, Collection)","u":"argNotEmpty(java.lang.String,java.util.Collection)"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotEmpty(String, String)","u":"argNotEmpty(java.lang.String,java.lang.String)"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotEmpty(String, T[])","u":"argNotEmpty(java.lang.String,T[])"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotNegative(String, float)","u":"argNotNegative(java.lang.String,float)"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotNegative(String, int)","u":"argNotNegative(java.lang.String,int)"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotNegative(String, long)","u":"argNotNegative(java.lang.String,long)"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argNotNull(String, T)","u":"argNotNull(java.lang.String,T)"},{"p":"org.hansken.plugin.extraction.util","c":"ArgChecks","l":"argsIsType(String, List, Class>)","u":"argsIsType(java.lang.String,java.util.List,java.lang.Class)"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"asBinary()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Direction","l":"ASCENDING"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"asVector(byte[])"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"author()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"author(Author)","u":"author(org.hansken.plugin.extraction.api.Author)"},{"p":"org.hansken.plugin.extraction.api","c":"BatchSearchResult","l":"BatchSearchResult(long)","u":"%3Cinit%3E(long)"},{"p":"org.hansken.plugin.extraction.api","c":"Author.Builder","l":"build()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"build()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources.Builder","l":"build()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions.Builder","l":"build()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Builder","l":"build()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation.Builder","l":"build()"},{"p":"org.hansken.plugin.extraction.api","c":"Author","l":"builder()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources","l":"builder()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"builder()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption","l":"builder()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation","l":"builder()"},{"p":"org.hansken.plugin.extraction.api","c":"Author.Builder","l":"Builder()","u":"%3Cinit%3E()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions.Builder","l":"Builder()","u":"%3Cinit%3E()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Builder","l":"Builder()","u":"%3Cinit%3E()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation.Builder","l":"Builder()","u":"%3Cinit%3E()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"builderFor(BaseExtractionPlugin)","u":"builderFor(org.hansken.plugin.extraction.api.BaseExtractionPlugin)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"builderFor(PluginType)","u":"builderFor(org.hansken.plugin.extraction.api.PluginType)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginId","l":"category()"},{"p":"org.hansken.plugin.extraction.api","c":"DataContext","l":"data()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"DataRange","l":"DataRange(long, long)","u":"%3Cinit%3E(long,long)"},{"p":"org.hansken.plugin.extraction.api","c":"DataContext","l":"dataType()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"DEFAULT"},{"p":"org.hansken.plugin.extraction.api","c":"PluginType","l":"DEFERRED_EXTRACTION_PLUGIN"},{"p":"org.hansken.plugin.extraction.api","c":"PluginType","l":"DEFERRED_META_EXTRACTION_PLUGIN"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"deferredIterations()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"deferredIterations(int)"},{"p":"org.hansken.plugin.extraction.api","c":"DeferredMetaExtractionPlugin","l":"DeferredMetaExtractionPlugin()","u":"%3Cinit%3E()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Direction","l":"DESCENDING"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"description()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"description(String)","u":"description(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption","l":"direction()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Builder","l":"direction(SearchSortOption.Direction)","u":"direction(org.hansken.plugin.extraction.api.SearchSortOption.Direction)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginId","l":"domain()"},{"p":"org.hansken.plugin.extraction.api","c":"Author","l":"email()"},{"p":"org.hansken.plugin.extraction.api","c":"Author.Builder","l":"email(String)","u":"email(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"enableBulkMode()"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"equals(Object)","u":"equals(java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"equals(Object)","u":"equals(java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption","l":"equals(Object)","u":"equals(java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"equals(Object)","u":"equals(java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginType","l":"EXTRACTION_PLUGIN"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption","l":"field()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Builder","l":"field(String)","u":"field(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"fullName()"},{"p":"org.hansken.plugin.extraction.api","c":"ImmutableTrace","l":"get(String)","u":"get(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.TraceletBuilder","l":"get(String)","u":"get(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"TransformerArgument","l":"getArgument()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchTrace","l":"getData(String)","u":"getData(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchTrace","l":"getDataTypes()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"DataRange","l":"getLength()"},{"p":"org.hansken.plugin.extraction.api","c":"TransformerLabel","l":"getMethodName()"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.Tracelet","l":"getName()"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.TraceletProperty","l":"getName()"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"getNewTracelets()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"DataRange","l":"getOffset()"},{"p":"org.hansken.plugin.extraction.api","c":"TransformerLabel","l":"getParameters()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation","l":"getRanges()"},{"p":"org.hansken.plugin.extraction.api","c":"TransformerLabel","l":"getReturnType()"},{"p":"org.hansken.plugin.extraction.api","c":"BatchSearchResult","l":"getTotalHits()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchResult","l":"getTotalHits()"},{"p":"org.hansken.plugin.extraction.api","c":"BatchSearchResult","l":"getTraces()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchResult","l":"getTraces()"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.TraceletBuilder","l":"getType()"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.Tracelet","l":"getValue()"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.TraceletProperty","l":"getValue()"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"hashCode()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"hashCode()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption","l":"hashCode()"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"hashCode()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"hqlMatcher()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"hqlMatcher(String)","u":"hqlMatcher(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"id()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"id(PluginId)","u":"id(org.hansken.plugin.extraction.api.PluginId)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"id(String, String, String)","u":"id(java.lang.String,java.lang.String,java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchScope","l":"IMAGE"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"isBulkModeEnabled()"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"latitude()"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"LatLong(double, double)","u":"%3Cinit%3E(double,double)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"license()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"license(String)","u":"license(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"longitude()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"maturityLevel()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"maturityLevel(MaturityLevel)","u":"maturityLevel(org.hansken.plugin.extraction.api.MaturityLevel)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources","l":"maximumCpu()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources.Builder","l":"maximumCpu(float)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources","l":"maximumMemory()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources.Builder","l":"maximumMemory(int)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources","l":"maximumWorkers()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginResources.Builder","l":"maximumWorkers(int)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginType","l":"META_EXTRACTION_PLUGIN"},{"p":"org.hansken.plugin.extraction.api","c":"MetaExtractionPlugin","l":"MetaExtractionPlugin()","u":"%3Cinit%3E()"},{"p":"org.hansken.plugin.extraction.api","c":"Author","l":"name()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginId","l":"name()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"name()"},{"p":"org.hansken.plugin.extraction.api","c":"Author.Builder","l":"name(String)","u":"name(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"name(String)","u":"name(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"newChild(String, ThrowingConsumer)","u":"newChild(java.lang.String,org.hansken.plugin.extraction.util.ThrowingConsumer)"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"of(Collection)","u":"of(java.util.Collection)"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"of(double, double)","u":"of(double,double)"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"of(float...)"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"of(String)","u":"of(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"ofBase64(String)","u":"ofBase64(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions.Builder","l":"offset(int)"},{"p":"org.hansken.plugin.extraction.api","c":"Author","l":"organisation()"},{"p":"org.hansken.plugin.extraction.api","c":"Author.Builder","l":"organisation(String)","u":"organisation(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginId","l":"PluginId(String, String, String)","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"BaseExtractionPlugin","l":"pluginInfo()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"pluginType()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"pluginVersion()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"pluginVersion(String)","u":"pluginVersion(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"position()"},{"p":"org.hansken.plugin.extraction.api","c":"MetaExtractionPlugin","l":"process(Trace)","u":"process(org.hansken.plugin.extraction.api.Trace)"},{"p":"org.hansken.plugin.extraction.api","c":"ExtractionPlugin","l":"process(Trace, DataContext)","u":"process(org.hansken.plugin.extraction.api.Trace,org.hansken.plugin.extraction.api.DataContext)"},{"p":"org.hansken.plugin.extraction.api","c":"MetaExtractionPlugin","l":"process(Trace, DataContext)","u":"process(org.hansken.plugin.extraction.api.Trace,org.hansken.plugin.extraction.api.DataContext)"},{"p":"org.hansken.plugin.extraction.api","c":"DeferredExtractionPlugin","l":"process(Trace, DataContext, TraceSearcher)","u":"process(org.hansken.plugin.extraction.api.Trace,org.hansken.plugin.extraction.api.DataContext,org.hansken.plugin.extraction.api.TraceSearcher)"},{"p":"org.hansken.plugin.extraction.api","c":"DeferredMetaExtractionPlugin","l":"process(Trace, DataContext, TraceSearcher)","u":"process(org.hansken.plugin.extraction.api.Trace,org.hansken.plugin.extraction.api.DataContext,org.hansken.plugin.extraction.api.TraceSearcher)"},{"p":"org.hansken.plugin.extraction.api","c":"DeferredMetaExtractionPlugin","l":"process(Trace, TraceSearcher)","u":"process(org.hansken.plugin.extraction.api.Trace,org.hansken.plugin.extraction.api.TraceSearcher)"},{"p":"org.hansken.plugin.extraction.api","c":"MaturityLevel","l":"PRODUCTION_READY"},{"p":"org.hansken.plugin.extraction.api","c":"SearchScope","l":"PROJECT"},{"p":"org.hansken.plugin.extraction.api","c":"MaturityLevel","l":"PROOF_OF_CONCEPT"},{"p":"org.hansken.plugin.extraction.api","c":"ImmutableTrace","l":"properties()"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation","l":"RangedDataTransformation(DataRange...)","u":"%3Cinit%3E(org.hansken.plugin.extraction.api.transformations.DataRange...)"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation","l":"RangedDataTransformation(List)","u":"%3Cinit%3E(java.util.List)"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"read(byte[])"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"read(byte[], int)","u":"read(byte[],int)"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"read(byte[], int, int)","u":"read(byte[],int,int)"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"readNBytes(int)"},{"p":"org.hansken.plugin.extraction.api","c":"MaturityLevel","l":"READY_FOR_TEST"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"remaining()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"resources()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"resources(PluginResources)","u":"resources(org.hansken.plugin.extraction.api.PluginResources)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"scope()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions.Builder","l":"scope(SearchScope)","u":"scope(org.hansken.plugin.extraction.api.SearchScope)"},{"p":"org.hansken.plugin.extraction.api","c":"TraceSearcher","l":"search(String)","u":"search(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"TraceSearcher","l":"search(String, int)","u":"search(java.lang.String,int)"},{"p":"org.hansken.plugin.extraction.api","c":"TraceSearcher","l":"search(String, int, SearchOptions)","u":"search(java.lang.String,int,org.hansken.plugin.extraction.api.SearchOptions)"},{"p":"org.hansken.plugin.extraction.api","c":"TraceSearcher","l":"search(String, int, SearchScope, int, List)","u":"search(java.lang.String,int,org.hansken.plugin.extraction.api.SearchScope,int,java.util.List)"},{"p":"org.hansken.plugin.extraction.api","c":"TraceSearcher","l":"search(String, SearchOptions)","u":"search(java.lang.String,org.hansken.plugin.extraction.api.SearchOptions)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"SearchOptions(SearchScope, int, List)","u":"%3Cinit%3E(org.hansken.plugin.extraction.api.SearchScope,int,java.util.List)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption","l":"SearchSortOption(String, SearchSortOption.Direction)","u":"%3Cinit%3E(java.lang.String,org.hansken.plugin.extraction.api.SearchSortOption.Direction)"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"seek(long)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"set(String, Object)","u":"set(java.lang.String,java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.TraceletBuilder","l":"set(String, Object)","u":"set(java.lang.String,java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"setData(String, DataTransformation...)","u":"setData(java.lang.String,org.hansken.plugin.extraction.api.transformations.DataTransformation...)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"setData(String, DataWriter)","u":"setData(java.lang.String,org.hansken.plugin.extraction.api.DataWriter)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"setData(String, InputStream)","u":"setData(java.lang.String,java.io.InputStream)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace","l":"setData(String, List)","u":"setData(java.lang.String,java.util.List)"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"DataRange","l":"setLength(long)"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"DataRange","l":"setOffset(long)"},{"p":"org.hansken.plugin.extraction.api.transformations","c":"RangedDataTransformation","l":"setRanges(List)","u":"setRanges(java.util.List)"},{"p":"org.hansken.plugin.extraction.api","c":"BatchSearchResult","l":"setTraces(SearchTrace[])","u":"setTraces(org.hansken.plugin.extraction.api.SearchTrace[])"},{"p":"org.hansken.plugin.extraction.api","c":"RandomAccessData","l":"size()"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"size()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"sort()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions.Builder","l":"sort(List)","u":"sort(java.util.List)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"start()"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"toBase64()"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"toISO6709()"},{"p":"org.hansken.plugin.extraction.api","c":"LatLong","l":"toString()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginId","l":"toString()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchOptions","l":"toString()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption","l":"toString()"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"toString()"},{"p":"org.hansken.plugin.extraction.api","c":"ImmutableTrace","l":"traceId()"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.Tracelet","l":"Tracelet(String, List)","u":"%3Cinit%3E(java.lang.String,java.util.List)"},{"p":"org.hansken.plugin.extraction.api","c":"Trace.TraceletProperty","l":"TraceletProperty(String, Object)","u":"%3Cinit%3E(java.lang.String,java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"transformer(List)","u":"transformer(java.util.List)"},{"p":"org.hansken.plugin.extraction.api","c":"TransformerArgument","l":"TransformerArgument(Object)","u":"%3Cinit%3E(java.lang.Object)"},{"p":"org.hansken.plugin.extraction.api","c":"TransformerLabel","l":"TransformerLabel(String, Map, String)","u":"%3Cinit%3E(java.lang.String,java.util.Map,java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"transformers()"},{"p":"org.hansken.plugin.extraction.api","c":"ImmutableTrace","l":"types()"},{"p":"org.hansken.plugin.extraction.api","c":"MaturityLevel","l":"valueOf(String)","u":"valueOf(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"PluginType","l":"valueOf(String)","u":"valueOf(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchScope","l":"valueOf(String)","u":"valueOf(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Direction","l":"valueOf(String)","u":"valueOf(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"MaturityLevel","l":"values()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginType","l":"values()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchScope","l":"values()"},{"p":"org.hansken.plugin.extraction.api","c":"SearchSortOption.Direction","l":"values()"},{"p":"org.hansken.plugin.extraction.api","c":"Vector","l":"values()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo","l":"webpageUrl()"},{"p":"org.hansken.plugin.extraction.api","c":"PluginInfo.Builder","l":"webpageUrl(String)","u":"webpageUrl(java.lang.String)"},{"p":"org.hansken.plugin.extraction.api","c":"DataWriter","l":"writeTo(OutputStream)","u":"writeTo(java.io.OutputStream)"}];updateSearchResults();
\ No newline at end of file
diff --git a/0.9.16/_static/javadoc/module-search-index.js b/0.9.16/_static/javadoc/module-search-index.js
new file mode 100644
index 0000000..0d59754
--- /dev/null
+++ b/0.9.16/_static/javadoc/module-search-index.js
@@ -0,0 +1 @@
+moduleSearchIndex = [];updateSearchResults();
\ No newline at end of file
diff --git a/0.9.16/_static/javadoc/org/hansken/plugin/extraction/api/Author.Builder.html b/0.9.16/_static/javadoc/org/hansken/plugin/extraction/api/Author.Builder.html
new file mode 100644
index 0000000..bca2455
--- /dev/null
+++ b/0.9.16/_static/javadoc/org/hansken/plugin/extraction/api/Author.Builder.html
@@ -0,0 +1,253 @@
+
+
+
+
+Author.Builder (Hansken Extraction Plugin SDK - API 0.9.16-master-188-037fa906 API)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This the base class for types of Extraction Plugins, and cannot be used solely as a superclass for a plugin.
+ Implement one of its subclasses that have a 'process' method.
+ Extraction plugins can be used by Hansken to process data during the extraction process.
The type of data (see data()) that is being processed. The type is never empty or null.
+ This is the type of data the given data sequence represents, for example "raw" for raw
+ bytes or "plain" for plaintext.
+
+ The data type will be one of those defined on the trace model associated with the trace being processed.
+ Note: the received output stream should not be closed by the user. It should
+ also only be used within the scope of the function, other usage may be guarded against or otherwise
+ result in undefined behaviour.
Deferred extraction plugins can be used by Hansken to process traces during the extraction process.
+ A processed trace can be enriched with new information and new child traces can also be created.
+
+ The difference between this and a normal ExtractionPlugin is that this plugin is able to run a secondary query
+ for traces and combine the results with previously retrieved traces.
+
+ When a plugin matches on the trace which is currently processed by Hansken
+ (for example, because it has certain properties), the plugin will receive the
+ matched trace in order to process it (see process(Trace, DataContext, TraceSearcher)).
+ Note: the given trace should only be modified within the scope of this method.
+ Any modifications afterward may be guarded against or result in undefined behaviour.
+
+
Parameters:
+
trace - the trace to process
+
dataContext - data context of this trace extraction
+
searcher - the searcher for the trace
+
Throws:
+
ExecutionException - when an exception occurs while searching for traces
+
InterruptedException - when a thread gets interrupted while searching for traces
Deferred meta extraction plugins can be used by Hansken to process traces during the extraction process.
+ A processed trace can be enriched with new information and new child traces can also be created.
+
+ The difference between this and a normal ExtractionPlugin is that this plugin is able to run a secondary query
+ for traces and combine the results with previously retrieved traces, and that this plugin does not receive or
+ process any data, only a trace itself.
+
+ When a plugin matches on the trace which is currently processed by Hansken
+ (for example, because it has certain properties), the plugin will receive the
+ matched trace in order to process it (see process(Trace, TraceSearcher)).
+
+ Note for Hansken core developers: specifying 'meta' in the matcher is
+ not necessary, the framework takes care of this.
+ Note: the given trace should only be modified within the scope of this method.
+ Any modifications afterward may be guarded against or result in undefined behaviour.
+ Note: the given trace should only be modified within the scope of this method.
+ Any modifications afterward may be guarded against or result in undefined behaviour.
+
+
Parameters:
+
trace - the trace to process
+
searcher - the searcher for the trace
+
Throws:
+
ExecutionException - when an exception occurs while searching for traces
+
InterruptedException - when a thread gets interrupted while searching for traces
Extraction plugins can be used by Hansken to process traces during the extraction process.
+ A processed trace can be enriched with new information and new child traces can also be created.
+
+ When a plugin matches on the trace which is currently processed by Hansken
+ (for example, because it has certain properties or a certain type of data sequence), the plugin will receive the
+ matched trace and data in order to process it (see process(Trace, DataContext)).
+ A trace can have multiple data sequences of different types. Because of this, a certain trace might
+ be processed multiple times (depending on if this plugin triggers on the different data types).
+
+ Note: the given trace should only be modified within the scope of this method.
+ Any modifications afterward may be guarded against or result in undefined behavior.
+
+
Parameters:
+
trace - the trace to process
+
dataContext - data context of the traces data stream that is being processed
A trace contains information about processed data. A trace should conform to the trace model defined by Hansken.
+ Unlike a Trace, an ImmutableTrace instance lacks methods to modify or update its properties and data
+ streams after the instance is created.
Get the value of the property with given name on this trace.
+
+ Note: the method is declared with a type parameter to allow for
+ automatic casting. It is an unchecked cast, so implementers have to make sure that
+ the type of the value returned is of type T.
public staticLatLongof(double latitude,
+ double longitude)
+
Create a new geographical point with given latitude
+ and longitude.
+
+ Note: this does not do any input validation. Valid
+ values should be between -90 and +90 inclusive for latitude and
+ between -180 and +180 inclusive for longitude.
Returns the enum constant of this class with the specified name.
+The string must match exactly an identifier used to declare an
+enum constant in this class. (Extraneous whitespace characters are
+not permitted.)
+
+
Parameters:
+
name - the name of the enum constant to be returned.
Meta extraction plugins can be used by Hansken to process traces during the extraction process.
+ A processed trace can be enriched with new information and new child traces can also be created.
+
+ The difference between this and a normal ExtractionPlugin is that this plugin does not receive
+ or process any data, only a trace itself.
+
+ When a plugin matches on the trace which is currently processed by Hansken
+ (for example, because it has certain properties), the plugin will receive the
+ matched trace in order to process it (see process(Trace)).
+
+ Note for Hansken core developers: specifying 'meta' in the matcher is
+ not necessary, the framework takes care of this.
+ A trace can have multiple data sequences of different types. Because of this, a certain trace might
+ be processed multiple times (depending on if this plugin triggers on the different data types).
+
+ Note: the given trace should only be modified within the scope of this method.
+ Any modifications afterward may be guarded against or result in undefined behavior.
Start processing a trace without any of its associated data streams.
+ When processing a given trace, new properties may be set on it. New children can be added using
+ Trace.newChild(String, ThrowingConsumer).
+
+ Note: the given trace should only be modified within the scope of this method.
+ Any modifications afterward may be guarded against or result in undefined behaviour.
Create a unique identifier for a plugin, consisting of domain, category and name.
+
+
Parameters:
+
domain - the domain of the organisation, for example "nfi.nl"
+
category - the action group of the plugin, for example `extract`, `carve`, `classify` (read the SDK documentation for more details).
+
name - the name of the plugin, or in the classic sense, a description detailing the action(s) of the plugin. Note that the name can contain (forward) slashes.
+ example: "nfi.nl/extract/ocr/detection/plugin".
+ in this example nfi.nl is the domain, extract is the category, and ocr/detection/plugin is the name.
Set the number of extraction iterations needed for this deferred plugin.
+ Only relevant for deferred plugins.
+ If this method is not called upon, default number of iterations will be set to 1.
+
+
Parameters:
+
deferredIterations - number of iterations needed for this plugin. Should be between 1 and 20
Set the unique id of this plugin, consisting of domain, category and name.
+
+ example: "nfi.nl/extract/ocr/detection/plugin".
+ in this example nfi.nl is the domain, extract is the category, and ocr/detection/plugin is the name.
+
+
Parameters:
+
domain - the domain of the organisation, for example "nfi.nl"
+
category - the action group of the plugin, for example `extract`, `carve`, `classify` (read the SDK documentation for more details).
+
name - the name of the plugin, or in the classic sense, a description detailing the action(s) of the plugin. Note that the name can contain (forward) slashes.
public final class PluginResources
+extends Object
+
PluginResources contains information about how many resources will be used for a plugin. The most common resources to specify are CPU and memory (RAM).
+
+ CPU resources are measured in cpu units. One cpu is equivalent to 1 vCPU/Core for cloud providers and 1 hyperthread on bare-metal Intel processors.
+ Also, fractional requests are allowed. A plugin that asks 0.5 CPU uses half as much CPU as one that asks for 1 CPU.
+
+ Memory resources are measured in Megabytes.
+
+ Here is an example to set resources for a plugin:
+
Returns the enum constant of this class with the specified name.
+The string must match exactly an identifier used to declare an
+enum constant in this class. (Extraneous whitespace characters are
+not permitted.)
+
+
Parameters:
+
name - the name of the enum constant to be returned.
Read from the data sequence, returning the read bytes as an array.The data will be read from the current
+ position and the amount of bytes read will equal count, unless the sequence contains
+ fewer remaining bytes.
Read bytes into the given buffer, starting at position 0 in the buffer. The data will be read from the
+ current position and the amount of bytes copied will equal the length of the buffer, unless
+ the data sequence contains fewer remaining bytes. In that case, data is read until the end of the sequence.
defaultintread(byte[] buffer,
+ int count)
+ throws IOException
+
Read bytes into the given buffer, starting at position 0 in the buffer. The data will be read from the
+ current position and the amount of bytes copied will equal count, unless the data
+ sequence contains fewer remaining bytes. In that case, data is read until the end of the sequence.
intread(byte[] buffer,
+ int offset,
+ int count)
+ throws IOException
+
Read data into the given buffer, starting at position offset in the buffer. The data will be read from
+ the current position and the amount of bytes read will equal count, unless the
+ sequence contains fewer remaining bytes. In that case, data is read until the end of the sequence.
+
+
Parameters:
+
buffer - the buffer to read into
+
offset - the offset in the buffer from which to start writing
+
count - the amount of bytes to read
+
Returns:
+
the number of bytes actually read
+
Throws:
+
IllegalArgumentException - if count or offset is negative,
+ or if offset + count is larger than the size of the buffer
Read from the data sequence, returning the read bytes as an array.The data will be read from the current
+ position and the amount of bytes read will equal count, unless the sequence contains
+ fewer remaining bytes. In that case, data is read until the end of the sequence and a smaller array is returned,
+ with a length equal to the number of read bytes.
+
+ Note: this method will allocate a new buffer each time it is called. It is intended for simple
+ cases where it is convenient to read a specified number of bytes into a byte array.
+
+
Parameters:
+
count - the amount of bytes to read
+
Returns:
+
a buffer containing the read bytes, or an empty array if we were at the end of the stream
Returns a string representation of this record class. The representation contains the name of the class, followed by the name and value of each of the record components.
Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. Reference components are compared with Objects::equals(Object,Object); primitive components are compared with '=='.
Returns the enum constant of this class with the specified name.
+The string must match exactly an identifier used to declare an
+enum constant in this class. (Extraneous whitespace characters are
+not permitted.)
+
+
Parameters:
+
name - the name of the enum constant to be returned.
Returns the enum constant of this class with the specified name.
+The string must match exactly an identifier used to declare an
+enum constant in this class. (Extraneous whitespace characters are
+not permitted.)
+
+
Parameters:
+
name - the name of the enum constant to be returned.
Returns a string representation of this record class. The representation contains the name of the class, followed by the name and value of each of the record components.
Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. All components in this record class are compared with Objects::equals(Object,Object).
A trace contains information about processed data. A trace should conform to the trace model defined by Hansken.
+ Unlike a Trace, the SearchTrace contains information about the searched trace. It is able to
+ directly retrieve all the data contexts belonging to a searched trace. When data type is known a
+ RandomAccessData can be retrieved directly.
public static class Trace.Tracelet
+extends Object
+
a Tracelet represents tracedata that can be present multiple times within a trace.
+ The API doesn't specify the cardinality , but the implementation is limited to
+ cardinality Few.
A trace contains information about processed data. A trace should conform to the trace model defined by Hansken.
+
+ A trace can have multiple types (e.g. file or chat). For these types, the trace can contain a set of properties and
+ associated values. A trace can be associated with a set of data sequences.
+ During extraction, an extraction plugin may receive a currently processed trace
+ and accompanying data sequence.
+
+ Implementers of a plugin should ensure that setting a property on a trace is valid as per the trace model
+ which is defined for the traces generated by the plugin.
+
+ Note: implementations are not required to implement any kind of thread safety.
+ It is up to the client to ensure this if necessary.
Add a data stream of a given type to this Trace (for example, 'raw' or 'html').
+ A trace can only have a single data stream for each data type. A callback function can be passed
+ which receives an OutputStream to write the data to.
+
+ Note: the received output stream should not be closed by the user. It should
+ also only be used within the scope of the callback, other usage may be guarded against or otherwise
+ result in undefined behaviour.
+
+ Example usage:
+
+ final PacketSequencer sequencer = ...;
+ final RandomAccessData input = dataContext.data();
+
+ trace.setData("packets", data -> {
+ sequencer.process(input).forEach(packet -> {
+ data.write(packet);
+ });
+ });
+
+
+
Parameters:
+
dataType - the type of the data stream to add
+
writer - callback that receives the stream to write to as input
Set a series of data transformations for a specific dataType.
+
+ Transformations are pointers to actual raw data. They describe how data can be obtained by reading on certain
+ positions, using decryption, or combinations of these. The benefit of using Transformations is that they take up
+ less space than the actual data.
Create and store new child trace of this trace. A callback function
+ can be passed in order to enrich the created child trace (which will have no types
+ or properties set yet), or even recursively add new children under it. After the
+ trace goes out of scope of the callback, it can no longer be updated.
+
+ As an example, say we have a Node type with the following API
+
Description of a transform method of a plugin.
+ The transform method is a method, specified by method name, of a Transformer capable plugin
+ that transforms input to output of a specified type (returnType)
Creates a Vector from a collection of numbers.
+ Note that all numbers are converted to floats internally, so loss of precision may occur when doubles or longs are offered.
Creates a vector from a binary representation.
+ Vector.asBinary() can be used to obtain a binary representation.
+ Note that this directly sets the internal state of the Vector, use {code asVector(bytes.clone()} to store a safe, immutable copy.
+
+
Parameters:
+
bytes - the bytes to convert to a vector.
+
Returns:
+
a vector
+
+
+
+
+
+
asBinary
+
publicbyte[]asBinary()
+
Returns the binary representation of the Vector.
+ Vector.asVector(byte[]) can be used to convert the binary representation back to the original vector.
+ The format of the returned bytes is a sequence of the floating point values of the vector, stored as big-endian IEEE 754 encoded 32-bit floating point values.
+ Note that this exposes the internal state of the Vector, use {code asBinary().clone()} to obtain a safely mutable copy.
+
+
Returns:
+
a binary representation of the vector.
+
+
+
+
+
+
size
+
publicintsize()
+
Returns the number of dimensions of the vector.
+
+
Returns:
+
the number of dimensions of the vector.
+
+
+
+
+
+
values
+
publicfloat[]values()
+
Returns the values of the Vector as an array of floats.
public static<T>TargNotNull(String name,
+ T value)
+
Check that the value with given name is not null, otherwise throw an exception.
+
+ Example usage:
+
+
+ private final Author author;
+
+ public Book(final Author author) {
+ // this throws a NullPointerException when the author is null
+ this.author = argNotNull("author", author);
+ }
+
+
+
+
Type Parameters:
+
T - the type of the value
+
Parameters:
+
name - a descriptive name for the value (most likely the argument name)
+
value - the value itself
+
Returns:
+
the value, so it can be used to inline in an assignment
public static<T>List<T>argsIsType(String name,
+ List<T> value,
+ Class<?> type)
+
Check that the value with given name is of type type, otherwise throw an exception.
+
+ Example usage:
+
+
+ public Book(final List<Object> authors) {
+ // this throws a ClassCastException when authors contains a type other than Author.class
+ argsIsType("author", authors, Author.class);
+ }
+
+
+
+
Type Parameters:
+
T - the type of the value
+
Parameters:
+
name - a descriptive name for the value (most likely the argument name)
+
value - the value itself
+
type - the class type we want to check against value
+
Returns:
+
the value, so it can be used to inline in an assignment
Check that not all values with given name are null, otherwise throw an exception.
+
+ Example usage:
+
+
+ private final Author author;
+
+ public Book(final Author... authors) {
+ // this throws a NullPointerException when the author is null
+ this.author = argNotAllNull("authors", authors);
+ }
+
+
+
+
Parameters:
+
name - a descriptive name for the values (most likely the argument name)
+
values - the values, which can be different types
+
Returns:
+
the values, so it can be used to inline in an assignment
public static<T>T[]argNotEmpty(String name,
+ T[] value)
+
Check that the array value with given name is not empty, otherwise throw an exception.
+
+ Example usage:
+
+
+ private final Author[] author;
+
+ public Book(final Author[] author) {
+ // this throws a NullPointerException when the author array is empty
+ this.author = argNotEmpty("author", author);
+ }
+
+
+
+
Type Parameters:
+
T - the type of the value
+
Parameters:
+
name - a descriptive name for the value (most likely the argument name)
+
value - the value itself
+
Returns:
+
the value, so it can be used to inline in an assignment
Check that the collection value with given name is not empty, otherwise throw an exception.
+
+ Example usage:
+
+
+ private final Collection<Author> author;
+
+ public Book(final Collection<Author> author) {
+ // this throws a NullPointerException when the author collection is empty
+ this.author = argNotEmpty("author", author);
+ }
+
+
+
+
Type Parameters:
+
T - the type of the value
+
Parameters:
+
name - a descriptive name for the value (most likely the argument name)
+
value - the value itself
+
Returns:
+
the value, so it can be used to inline in an assignment
Check that the string with given name is not null or empty, otherwise throw an exception.
+
+ Example usage:
+
+
+ private final String title;
+
+ public Book(final String title) {
+ // this throws a NullPointerException when the author is null
+ // or an IllegalArgumentException when author is an empty String
+ this.title = argNotEmpty("title", title);
+ }
+
+
+
+
Parameters:
+
name - a descriptive name for the string (most likely the argument name)
+
value - the string itself
+
Returns:
+
the string, so it can be used to inline in an assignment
public staticintargNotNegative(String name,
+ int value)
+
Check that the int with given name is not negative, otherwise throw an exception.
+ Example usage:
+
+
+ private final int pageCount;
+
+ public Book(final int pageCount) {
+ // this throws an IllegalArgumentException when pageCount is negative
+ this.pageCount = argNotNegative("pageCount", pageCount);
+ }
+
+
+
+
Parameters:
+
name - a descriptive name for the int (most likely the argument name)
+
value - the int itself
+
Returns:
+
the int, so it can be used in an inline assignment
public staticlongargNotNegative(String name,
+ long value)
+
Check that the long with given name is not negative, otherwise throw an exception.
+ Example usage:
+
+
+ private final long pageCount;
+
+ public Book(final long pageCount) {
+ // this throws an IllegalArgumentException when pageCount is negative
+ this.pageCount = argNotNegative("pageCount", pageCount);
+ }
+
+
+
+
Parameters:
+
name - a descriptive name for the long (most likely the argument name)
+
value - the long itself
+
Returns:
+
the long, so it can be used in an inline assignment
Represents an operation that accepts a single input argument and returns no result.
+ Replaces the normal Java Consumer and enables throwing an Exception from the callback. Used to enable
+ lambda callbacks in for example Trace.newChild(String, ThrowingConsumer).
The help page provides an introduction to the scope and syntax of JavaDoc search.
+
You can use the <ctrl> or <cmd> keys in combination with the left and right arrow keys to switch between result tabs in this page.
+
The URL template below may be used to configure this page as a search engine in browsers that support this feature. It has been tested to work in Google Chrome and Mozilla Firefox. Note that other browsers may not support this feature or require a different URL format.
+link
+
+
+
+
+
Loading search index...
+
+
+
+
+
+
+
+
+
+
diff --git a/0.9.16/_static/javadoc/search.js b/0.9.16/_static/javadoc/search.js
new file mode 100644
index 0000000..d398670
--- /dev/null
+++ b/0.9.16/_static/javadoc/search.js
@@ -0,0 +1,458 @@
+/*
+ * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+"use strict";
+const messages = {
+ enterTerm: "Enter a search term",
+ noResult: "No results found",
+ oneResult: "Found one result",
+ manyResults: "Found {0} results",
+ loading: "Loading search index...",
+ searching: "Searching...",
+ redirecting: "Redirecting to first result...",
+ linkIcon: "Link icon",
+ linkToSection: "Link to this section"
+}
+const categories = {
+ modules: "Modules",
+ packages: "Packages",
+ types: "Classes and Interfaces",
+ members: "Members",
+ searchTags: "Search Tags"
+};
+const highlight = "$&";
+const NO_MATCH = {};
+const MAX_RESULTS = 300;
+function checkUnnamed(name, separator) {
+ return name === "" || !name ? "" : name + separator;
+}
+function escapeHtml(str) {
+ return str.replace(//g, ">");
+}
+function getHighlightedText(str, boundaries, from, to) {
+ var start = from;
+ var text = "";
+ for (var i = 0; i < boundaries.length; i += 2) {
+ var b0 = boundaries[i];
+ var b1 = boundaries[i + 1];
+ if (b0 >= to || b1 <= from) {
+ continue;
+ }
+ text += escapeHtml(str.slice(start, Math.max(start, b0)));
+ text += "";
+ text += escapeHtml(str.slice(Math.max(start, b0), Math.min(to, b1)));
+ text += "";
+ start = Math.min(to, b1);
+ }
+ text += escapeHtml(str.slice(start, to));
+ return text;
+}
+function getURLPrefix(item, category) {
+ var urlPrefix = "";
+ var slash = "/";
+ if (category === "modules") {
+ return item.l + slash;
+ } else if (category === "packages" && item.m) {
+ return item.m + slash;
+ } else if (category === "types" || category === "members") {
+ if (item.m) {
+ urlPrefix = item.m + slash;
+ } else {
+ $.each(packageSearchIndex, function(index, it) {
+ if (it.m && item.p === it.l) {
+ urlPrefix = it.m + slash;
+ }
+ });
+ }
+ }
+ return urlPrefix;
+}
+function getURL(item, category) {
+ if (item.url) {
+ return item.url;
+ }
+ var url = getURLPrefix(item, category);
+ if (category === "modules") {
+ url += "module-summary.html";
+ } else if (category === "packages") {
+ if (item.u) {
+ url = item.u;
+ } else {
+ url += item.l.replace(/\./g, '/') + "/package-summary.html";
+ }
+ } else if (category === "types") {
+ if (item.u) {
+ url = item.u;
+ } else {
+ url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.l + ".html";
+ }
+ } else if (category === "members") {
+ url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.c + ".html" + "#";
+ if (item.u) {
+ url += item.u;
+ } else {
+ url += item.l;
+ }
+ } else if (category === "searchTags") {
+ url += item.u;
+ }
+ item.url = url;
+ return url;
+}
+function createMatcher(term, camelCase) {
+ if (camelCase && !isUpperCase(term)) {
+ return null; // no need for camel-case matcher for lower case query
+ }
+ var pattern = "";
+ var upperCase = [];
+ term.trim().split(/\s+/).forEach(function(w, index, array) {
+ var tokens = w.split(/(?=[A-Z,.()<>?[\/])/);
+ for (var i = 0; i < tokens.length; i++) {
+ var s = tokens[i];
+ // ',' and '?' are the only delimiters commonly followed by space in java signatures
+ pattern += "(" + $.ui.autocomplete.escapeRegex(s).replace(/[,?]/g, "$&\\s*?") + ")";
+ upperCase.push(false);
+ var isWordToken = /\w$/.test(s);
+ if (isWordToken) {
+ if (i === tokens.length - 1 && index < array.length - 1) {
+ // space in query string matches all delimiters
+ pattern += "(.*?)";
+ upperCase.push(isUpperCase(s[0]));
+ } else {
+ if (!camelCase && isUpperCase(s) && s.length === 1) {
+ pattern += "()";
+ } else {
+ pattern += "([a-z0-9$<>?[\\]]*?)";
+ }
+ upperCase.push(isUpperCase(s[0]));
+ }
+ } else {
+ pattern += "()";
+ upperCase.push(false);
+ }
+ }
+ });
+ var re = new RegExp(pattern, "gi");
+ re.upperCase = upperCase;
+ return re;
+}
+function findMatch(matcher, input, startOfName, endOfName) {
+ var from = startOfName;
+ matcher.lastIndex = from;
+ var match = matcher.exec(input);
+ // Expand search area until we get a valid result or reach the beginning of the string
+ while (!match || match.index + match[0].length < startOfName || endOfName < match.index) {
+ if (from === 0) {
+ return NO_MATCH;
+ }
+ from = input.lastIndexOf(".", from - 2) + 1;
+ matcher.lastIndex = from;
+ match = matcher.exec(input);
+ }
+ var boundaries = [];
+ var matchEnd = match.index + match[0].length;
+ var score = 5;
+ var start = match.index;
+ var prevEnd = -1;
+ for (var i = 1; i < match.length; i += 2) {
+ var isUpper = isUpperCase(input[start]);
+ var isMatcherUpper = matcher.upperCase[i];
+ // capturing groups come in pairs, match and non-match
+ boundaries.push(start, start + match[i].length);
+ // make sure groups are anchored on a left word boundary
+ var prevChar = input[start - 1] || "";
+ var nextChar = input[start + 1] || "";
+ if (start !== 0 && !/[\W_]/.test(prevChar) && !/[\W_]/.test(input[start])) {
+ if (isUpper && (isLowerCase(prevChar) || isLowerCase(nextChar))) {
+ score -= 0.1;
+ } else if (isMatcherUpper && start === prevEnd) {
+ score -= isUpper ? 0.1 : 1.0;
+ } else {
+ return NO_MATCH;
+ }
+ }
+ prevEnd = start + match[i].length;
+ start += match[i].length + match[i + 1].length;
+
+ // lower score for parts of the name that are missing
+ if (match[i + 1] && prevEnd < endOfName) {
+ score -= rateNoise(match[i + 1]);
+ }
+ }
+ // lower score if a type name contains unmatched camel-case parts
+ if (input[matchEnd - 1] !== "." && endOfName > matchEnd)
+ score -= rateNoise(input.slice(matchEnd, endOfName));
+ score -= rateNoise(input.slice(0, Math.max(startOfName, match.index)));
+
+ if (score <= 0) {
+ return NO_MATCH;
+ }
+ return {
+ input: input,
+ score: score,
+ boundaries: boundaries
+ };
+}
+function isUpperCase(s) {
+ return s !== s.toLowerCase();
+}
+function isLowerCase(s) {
+ return s !== s.toUpperCase();
+}
+function rateNoise(str) {
+ return (str.match(/([.(])/g) || []).length / 5
+ + (str.match(/([A-Z]+)/g) || []).length / 10
+ + str.length / 20;
+}
+function doSearch(request, response) {
+ var term = request.term.trim();
+ var maxResults = request.maxResults || MAX_RESULTS;
+ if (term.length === 0) {
+ return this.close();
+ }
+ var matcher = {
+ plainMatcher: createMatcher(term, false),
+ camelCaseMatcher: createMatcher(term, true)
+ }
+ var indexLoaded = indexFilesLoaded();
+
+ function getPrefix(item, category) {
+ switch (category) {
+ case "packages":
+ return checkUnnamed(item.m, "/");
+ case "types":
+ return checkUnnamed(item.p, ".");
+ case "members":
+ return checkUnnamed(item.p, ".") + item.c + ".";
+ default:
+ return "";
+ }
+ }
+ function useQualifiedName(category) {
+ switch (category) {
+ case "packages":
+ return /[\s/]/.test(term);
+ case "types":
+ case "members":
+ return /[\s.]/.test(term);
+ default:
+ return false;
+ }
+ }
+ function searchIndex(indexArray, category) {
+ var matches = [];
+ if (!indexArray) {
+ if (!indexLoaded) {
+ matches.push({ l: messages.loading, category: category });
+ }
+ return matches;
+ }
+ $.each(indexArray, function (i, item) {
+ var prefix = getPrefix(item, category);
+ var simpleName = item.l;
+ var qualifiedName = prefix + simpleName;
+ var useQualified = useQualifiedName(category);
+ var input = useQualified ? qualifiedName : simpleName;
+ var startOfName = useQualified ? prefix.length : 0;
+ var endOfName = category === "members" && input.indexOf("(", startOfName) > -1
+ ? input.indexOf("(", startOfName) : input.length;
+ var m = findMatch(matcher.plainMatcher, input, startOfName, endOfName);
+ if (m === NO_MATCH && matcher.camelCaseMatcher) {
+ m = findMatch(matcher.camelCaseMatcher, input, startOfName, endOfName);
+ }
+ if (m !== NO_MATCH) {
+ m.indexItem = item;
+ m.prefix = prefix;
+ m.category = category;
+ if (!useQualified) {
+ m.input = qualifiedName;
+ m.boundaries = m.boundaries.map(function(b) {
+ return b + prefix.length;
+ });
+ }
+ matches.push(m);
+ }
+ return true;
+ });
+ return matches.sort(function(e1, e2) {
+ return e2.score - e1.score;
+ }).slice(0, maxResults);
+ }
+
+ var result = searchIndex(moduleSearchIndex, "modules")
+ .concat(searchIndex(packageSearchIndex, "packages"))
+ .concat(searchIndex(typeSearchIndex, "types"))
+ .concat(searchIndex(memberSearchIndex, "members"))
+ .concat(searchIndex(tagSearchIndex, "searchTags"));
+
+ if (!indexLoaded) {
+ updateSearchResults = function() {
+ doSearch(request, response);
+ }
+ } else {
+ updateSearchResults = function() {};
+ }
+ response(result);
+}
+// JQuery search menu implementation
+$.widget("custom.catcomplete", $.ui.autocomplete, {
+ _create: function() {
+ this._super();
+ this.widget().menu("option", "items", "> .result-item");
+ // workaround for search result scrolling
+ this.menu._scrollIntoView = function _scrollIntoView( item ) {
+ var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+ if ( this._hasScroll() ) {
+ borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
+ paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
+ offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
+ scroll = this.activeMenu.scrollTop();
+ elementHeight = this.activeMenu.height() - 26;
+ itemHeight = item.outerHeight();
+
+ if ( offset < 0 ) {
+ this.activeMenu.scrollTop( scroll + offset );
+ } else if ( offset + itemHeight > elementHeight ) {
+ this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
+ }
+ }
+ };
+ },
+ _renderMenu: function(ul, items) {
+ var currentCategory = "";
+ var widget = this;
+ widget.menu.bindings = $();
+ $.each(items, function(index, item) {
+ if (item.category && item.category !== currentCategory) {
+ ul.append("
The following page lists all (technical) changes in the extraction plugin SDK.
+
Programming language specific API changes are described in more detail on API changelog pages.
+These pages list new API functionalities, and describe how to update your plugins when API changes are in order.
+For the API changelog pages see:
HANSKEN-20128: Scope trace searches by default to the image under extraction, but allow project-wide searches by passing an optional argument (Java).
+
HANSKEN-20127: Scope trace searches by default to the image under extraction, but allow project-wide searches by passing an optional argument (Python).
+
HANSKEN-20552: Support trace property types of list[float] (Python), List (Java), List (Java), and List (Java).
HANSKEN-16632: Fix execution of meta-extraction plugins with hansken.py runner which failed with an ‘expected $data in matcher’ error
+
HANSKEN-16634: Allow forward compatibility with Hansken by introducing an GRPC API version
+
HANSKEN-16489: Let build pipeline publish Java artifacts to community
+
HANSKEN-16558: Serve SDK test framework errors in a more developer-friendly way
+
HANSKEN-16489: Removed incompatibility warning for All In One with Hansken.py
+
HANSKEN-16403: Fixed running markdownlint with tox-emarkdownlint
+
HANSKEN-16268: Added * value support to the HQL-Lite term matcher, improved documentation by using HQL default property:value instead of property=value
+
HANSKEN-16258: Fixed Jenkins build
+
HANSKEN-16257: Fixed docker stop command in test framework
HANSKEN-14879: Allow SDK releases to be published on PyPI
+
HANSKEN-14738: Shade NFI internal projects into the SDK testframework jar
+
HANSKEN-14841: Bugfix where RpcStringMap wasn’t being unpacked properly in Python, which was discovered during a flits test.
+
HANSKEN-14703: Let Python plugins exit gracefully on SIGTERM
+
HANSKEN-14844: SDK: move serve from test_framework to runtime
+
HANSKEN-14793: Add ExtractionPluginBuilder.add_data method in python API
+
HANSKEN-14777: Add an extra check to ignore and log unsupported types during RpcStart gRPC serialization.
+
HANSKEN-14739: License: Distribute ExtractionPluginSDK under the Apache License 2.0
+
HANSKEN-14763: Bugfix where some python plugins were not loaded correctly when using serve or test-plugin commands
+
HANSKEN-14737: Move serve.py from plugin examples to SDK
+
HANSKEN-14720: Add option to use with when using the trace.open method in python
+
HANSKEN-14582: Add option to write data using the python api
+
HANSKEN-14660: Move _test.py files from plugin examples repo to SDK repo
+
HANSKEN-14618: Add validation for unexpected extra data streams to test framework
+
HANSKEN-14704: Fix shading of the runtime super pom
+
HANSKEN-14619: Allow propagation of IOException in plugin new child callback
+
HANSKEN-14632: Add Java gRPC support for writing raw data streams on a trace
+
HANSKEN-14591: Split into three modules in the SDK
+
HANSKEN-14580: Add proto message definitions for raw data stream writing
+
HANSKEN-14635: Trace format which containes name/id can now be deserialized by testframework
+
HANSKEN-14131: Added ‘verbose’ logging for test-framework HQL matching
+
HANSKEN-14130: Updated StandaloneTestRunner to expose more errors & exceptions
+
HANSKEN-13784: Add meta support to test-framework
+
HANSKEN-14581: Extend Trace API with raw data writing capabilities
+
HANSKEN-14547: Deploy Java sources JAR for improved client debugging
+
HANSKEN-14531: Fix Python release. Python needs only one build step, which is either a snapshot or a release build. The separate Python release step was removed and merged with the first Python build step. Repository paths were corrected for the release version.
+
HANSKEN-14531: Fix python release - The python release is no longer a separate step in the build pipeline, since there is no actual difference between a snapshot and a release, apart from the version numbering scheme and the test-framework is downloaded from a repository location depending on the release parameter (see comments in Jenkinsfile)
+
HANSKEN-14161: Add Python test-framework wrapper around Java test-framework and add test-framework.tgz to whl
+
HANSKEN-14234: Restructure build pipeline to build and release Java first
+
HANSKEN-13799: Extraction Plugin: support meta extraction
+
HANSKEN-14286: Create adapter from RandomAccessData to InputStream
+
HANSKEN-14314: Don’t send child name when sending enrichment message
+
HANSKEN-14318: Flush cached children before flushing root in case of error with python gRPC server
+
HANSKEN-13414: Generate shaded jar for runtime that shades Guava, Protobuf, gRPC, and Netty (fix)
+
HANSKEN-14283: Allow passing a configuration of retry policy for the extraction plugin client
+
HANSKEN-14234: Restructure build pipeline to build and release Java first
+
HANSKEN-13414: Generate shaded jar for runtime that shades Guava, Protobuf, gRPC, and Netty
+
HANSKEN-14234: Restructure build pipeline to build and release Java first
+
HANSKEN-14135: Improve testing Python plugins in integration step and test reading large chunks
+
HANSKEN-14134: Fix releasing python plugins
+
HANSKEN-14128: Validate gRPC message limit for Python server instances
+
HANSKEN-14122: Fix missing comma in dependencies which broke the release
+
HANSKEN-14092: The type and total size of the data currently being processed can now be retrieved from an extractioncontext object passed to the process function
+
HANSKEN-13668: Added support for lists of longs, Hansken maps and LatLong to Java and Python API
+
HANSKEN-14104: Set Python gRPC limit to 64 MB(including message overhead)
+
HANSKEN-14079: Add logging to the SDK
+
HANSKEN-14010: Add support for serializing datetime in python API
+
HANSKEN-14083: Fix releasing python sdk
+
HANSKEN-14035: Add static type checks to python project
+
HANSKEN-14030: Allow test framework to be executed standalone for non-java extraction plugins
+
HANSKEN-14073: Support gRPC extraction plugins in test framework
+
HANSKEN-14074: Add support for serialization of Maps.
+
Hansken-14090: Use new Hansken python-api children call for creating nested children
+
HANSKEN-13774: Add support for creating children in python API
+
HANSKEN-13776: Add error handling to the Python based server and send error messages to the client
+
HANSKEN-14060: Remove mapping-interface from python extraction for API consistency
+
HANSKEN-13773: Update trace properties in Python API
+
HANSKEN-14044: Make sure python testing code is linted as well, enforce single quotes
+
HANSKEN-13772: Expose trace properties in Python API
+
HANSKEN-13775: Python - added trace.open() functionality to read from data streams
+
HANSKEN-14031: Split Trace interfaces (hansken.py trace vs external plugin trace)
+
HANSKEN-14037: Make sure pytest is always used for python tests
+
HANSKEN-14011: Implement unpack for trace
+
HANSKEN-14009: Implement pack for trace and trace enrichment
+
HANSKEN-14008: Move generated hql-lite parsers to different package (conflicts with hql package)
+
HANSKEN-13777: Added utility to run Python Extraction Plugin implementations with Hansken.py
+
HANSKEN-13771: Implement Extraction Plugin Info for Python plugins
+
HANSKEN-13966: Give socketproxy disconnect some time to disconnect (fixes flaky unit test)
+
HANSKEN-13810: Add extraction plugin python server code
+
HANSKEN-13676: Added support for ZonedDateTime over gRPC
+
HANSKEN-13922: Add webpage url to PluginInfo
+
HANSKEN-13676: Changed the way of creating child Traces to using a consumer.
+
HANSKEN-13655: Added server/client disconnect tests and implemented initial handling server-side
+
HANSKEN-13809: Added missing gRPC exception handles of process()
+
HANSKEN-13801: Made HQL-Lite matchers immutable
+
HANSKEN-13761: Seperated HQL-Lite type matcher implementation
+
HANSKEN-13756: Added HQL-Lite datastream matchers
+
HANSKEN-13800: (Temporarily) remove meta from Extraction Plugin API
+
HANSKEN-13798: Propagate exception on failure of START serialization
+
HANSKEN-13706: Create basic test framework implementation
+
HANSKEN-13769: Make jenkins run tests and a linter for python
+
HANSKEN-13705: Add support for creating child traces over gRPC
+
HANSKEN-13713: Make (partial) extraction plugin errors visible for clients
+
HANSKEN-13709: Send partial result when an external plugin errors out
+
HANSKEN-13733: Add script to generate gRPC Python files
+
HANSKEN-13660: Copied the Hql definition from Hansken to enable the HQL-Lite implementation
+
HANSKEN-13656: Test and handle invalid protocol messages
+
HANSKEN-13663: Add matcher interface to PluginInfo
+
HANSKEN-13714: Add IOException to ExtractionPlugin.process() interface
+
HANSKEN-13658: test(s) for non-grpc connection with a grpc server
+
HANSKEN-13650: Add basic support for writing trace information over gRPC
+
HANSKEN-13648: Add basic support for reading trace information over gRPC
+
HANSKEN-13651: Default implementations for RandomAccessData interface
+
HANSKEN-13643: Add basic support for reading trace information over gRPC
+
HANSKEN-13643: Add basic support for reading from trace data over gRPC
Chat with us on Discord. You can find members of the extraction plugin SDK development team
+in the Hansken Community server, in the extraction-plugins channel. This is a Hansken community-private server. If you
+don’t have access to the Hansken Community server, please contact your Hansken business owner. He or she can invite you
+to the Hansken Community server. If you don’t know who to contact, feel free to fill in
+the contact form for further questions/contact.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/concepts.html b/0.9.16/dev/concepts.html
new file mode 100644
index 0000000..ba7d01e
--- /dev/null
+++ b/0.9.16/dev/concepts.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+ General concepts — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The pluginInfo() method returns a PluginInfo object. Hansken needs this object to be able to know the capabilities
+of the plugin, and to show the plugin in the list of tools. The most important fields that must be set on PluginInfo
+are the following:
+
+
id | The identifier of the plugin. This will be used as a unique name for the plugin Hansken. |
+
description | A description of the plugin that is shown in Hansken. |
+
author | The author of the plugin that is shown in Hansken. |
+
license | The type of license of the plugin that is shown in Hansken. |
+
matcher | This matcher is used by Hansken to determine which Traces are sent to the Plugin during extraction. |
During extraction, Hansken calls the process() method for every matching trace. The matcher attribute of
+the PluginInfo is very important as it determines which traces will be sent to the process method.
+
Although the plugin developer is free to program whatever seems useful, the following tasks are typically performed
+within the process() method:
+
+
Creating child-traces
+
Reading trace properties
+
Adding trace properties
+
Reading the data that the Trace represents
+
Writing data on a Trace
+
+
Depending on the type of plugin that is implemented, different functionality is available in the process() method.
+See Plugin Types for more details.
This describes the process of running a plugin from the perspective of Hansken. The perspective of the user is described
+in Hansken Extraction Plugins.
Hansken manages a list of tools that can be used in extractions. The available plugins must be added to this list so
+that the user can select them. To accomplish this, Hansken scans the Docker registry for docker images that are plugins.
+Each image is started up, and a call is done to its pluginInfo() method. If the call resulted in a valid PluginInfo
+object, the Extraction Plugin is added to the list of tools visible to users. After the PluginInfo is retrieved, the
+docker image is shutdown again.
Hansken checks if any plugins are selected by the user that started the extraction. For each selected plugin, at least
+one docker image will be started. See Kubernetes autoscaling for more details on expanding
+the number of instances for each plugin.
During an extraction, Hansken will iteratively loop through all selected tools, including Extraction Plugins. For each
+trace that matches on a tool, Hansken will call its process method. For Extraction Plugins, this means that
+the process method is called via the gRPC protocol. The trace to be processed is sent over gRPC to the plugin, and any
+other communication between Hansken and the Extraction Plugin (like created properties and child traces, search requests
+and written data) are done using gRPC.
Extraction Plugins can create new data-streams on a Trace through data transformations. Data
+transformations describe how data can be obtained from a source. Data transformations are preferred over storing blobs
+because they take less space. This is because they only describe the data instead of specifying the actual data.
+
The following figure shows how Hansken visualizes data transformations:
+
+
Note that transformations can be applied on transformations. The SDK only supports range transformations at the moment,
+while this image also shows some transformations that are currently available in Hansken but not in the SDK.
+
An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in
+the archive file. Each child trace will have a data stream that is a transformation that marks the start and length of
+the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of
+space is saved.
+
Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data
+transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in a
+bytearray.
Hansken Extraction Plugins can be built in Java or Python by implementing an interface. Which interface you choose
+depends on the type of plugin you choose to make, see Plugin Types. For more information on coding
+your own plugin, see the Extraction Plugin Examples.
+
The plugin can then be tested using the Test Framework. This way you make sure everything works as
+expected before taking further steps.
To upload a plugin into Hansken, a docker image for this plugin must be uploaded to the docker registry. First, the
+plugin container image must be packaged.
+A plugin is packaged into an OCI image (also known as Docker image).
Hansken finds the plugins by scanning a docker registry. It will try to load all docker images with a certain prefix as
+Extraction Plugins. The settings for this are defined in Hansken properties:
+
+
registry.extraction.plugins.registry.uri defines the registry
+
registry.extraction.plugins.registry.prefix defines the prefix plugins must have
+
+
When the image is packaged locally, it needs to be pushed (uploaded) to the docker registry. These commands provide an
+outline of how to do this:
+
+
dockerlogin<docker-registry> (make sure you are logged in to the registry)
+
dockerpush<docker-registry><prefix><pluginname> (push the plugin docker image to the registry)
+
+
+
Note
+
For more information about uploading plugins and running them in Hansken, see the ‘Using extraction plugins in
+Hansken’ chapter of the Hansken User Guide.
Hansken checks which plugins are available at startup. The list of available plugins can also be refreshed by calling
+the following endpoint: <hansken-domain>/gatekeeper/tools?refresh=true
If everything went well, the list of available tools in Hansken should now feature your plugin. To run the plugin in an
+extraction, be sure to select its checkbox in the extraction tools dialog.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/concepts/hql_lite.html b/0.9.16/dev/concepts/hql_lite.html
new file mode 100644
index 0000000..46dee20
--- /dev/null
+++ b/0.9.16/dev/concepts/hql_lite.html
@@ -0,0 +1,575 @@
+
+
+
+
+
+
+
+
+ HQL-Lite — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
HQL-Lite is a query language derived from Hanskens full HQL human. HQL stands for Hansken Query Language and can be
+used to search or match traces. Since not all elements of full HQL can be used in the context of an extraction,
+extraction plugins use HQL-Lite, a lightweight version of HQL. This document describes the usage of HQL-Lite in the
+context of extraction plugins.
To reduce the unnecessary overhead of processing all traces (even the ones the tool cannot actually process), Hansken
+implements the concept of a matcher for each tool. This matcher basically checks the trace for “matching
+conditions”, that would allow the tool to process it.
+
Sometimes these matching conditions can be as simple as a specific filename or extension, but are often more
+elaborate in the sense that they check multiple factors that require some intimate knowledge of Hansken.
HQL-Lite is a language based on HQL (Hansken Query Language) that allows plugin developers to write matchers for
+Hansken Extraction Plugins. It could be said that HQL-Lite contains a subset of HQL features, plus some HQL-Lite unique
+features that are only interesting for matchers.
+
+
Note
+
Please note that even though the HQL-Lite query is part of the plugin, it is compiled and stored in Hansken during
+startup to achieve performance.
HQL was designed to search for traces stored in the Elasticsearch database. As such, some of its features are tightly
+coupled to the Elasticsearch implementation, making it difficult to re-implement them for plugins.
+
Also, even though HQL is more complex than the requirements for matching in plugins, a couple of minor features that
+are absolutely necessary for matching are not implemented in HQL, as they don’t make much sense from a search point of
+view. This is because HQL was designed to be used with finished extractions with all the traces stored in the
+database, while HQL-Lite was designed for active extractions.
an empty string translates to match for all traces
+
+
And
+
foo:1ANDbar:2
+
the case-sensitive AND operator behaves like a logical AND of 2 conditions
+
+
Not
+
NOTfoo or -foo
+
the case-sensitive NOT or - negates the expression that follows
+
+
Range
+
foo>1 or 1<=foo<10
+
a numbered-range check with a min or/and max range(s)
+
+
Or
+
foo:1ORbar:2
+
the case-sensitive OR operator behaves like a logical OR of 2 conditions
+
+
Data
+
$data.foo:1
+
see $data section below
+
+
DataType
+
$data.type:raw
+
this query matches against the type of the current datastream
+
+
Types
+
type:email
+
this query checks if the trace contains a certain trace type as defined in the Hansken trace model
+
+
+
+
There are also a couple of general guidelines that apply to all matchers:
+
+
Equals/not equals:
+
+
: or = : The most basic of left equals right statements. note that = is also valid.
+
!= : The opposite of equals, not equals. Note that !: is NOT supported.
+
+
+
Wildcards:
+
+
? : Match against any single character. E.g. foo:r?w will match against raw,row but not against rowing.
+
* : Match against any chars. E.g. foo:r* will match against r,ra,raw,raaaaaw but not against aw.
+
+
+
Exact match: By surrounding a value with quotes, we tell the parser that it is a single value. This is especially
+helpful for values that might contain separators. E.g. foo:'hellohql-lite'.
+
CSV: Currently only the type query supports multiple values to check against. E.g. type:email,chatMessage will only
+return true if both types exist for this trace.
+
() grouping: You can group statements by putting brackets around them. E.g. foo:1AND(bar:2ORbla:3) which
+translates to foo:1 plus one of the statements in the brackets.
+
Escaping \"\.\t\r\n:=><!()~/,[]{}: Some characters are used internally by HQL-Lite, and need to be escaped if they
+are used in the value side of the key-value pair. These values can be escaped by adding prepending \\ to the
+character(s). Example: foo:foobar should be foo:foo\\bar, foo.bar:foo:bar should be foo.bar:foo\\:bar
+…etc.
+
+
The only exceptions to this rule are unix paths:
+
+
Acceptable paths:
+
+
foo:/
+
foo:bar/baz
+
foo:/bar/baz
+
foo:'/bar/baz/hello'
+
foo:*bar/baz*
+
+
+
Unacceptable paths:
+
+
foo:/bar/ -> this is the regex matcher, which is unsupported in HQL-lite
+
foo:c:\ -> should be foo:c\:\\, both the colon and the slash need to be escaped
+
foo:'c:\' -> should be foo:'c:\\', the slash still needs to be escaped
+
+
+
Note
+
the backslash is the universal escape character, so it always needs to be escaped.
In Hansken, a trace can have multiple datastreams. The exact content of said datastreams is
+discussed elsewhere, but the basic idea is that a trace can have multiple representations. For example, a trace might
+have a raw datastream, but after we identify that the raw bytes contain a text file, we might add a separate
+datastream text.
+
+
Note
+
The process() method of each plugin is called for each datastream of each trace. This is explained
+in How does Hansken work? . Subsequently, you might have the same property for a
+different datastream. For example: you might have a data.raw.size and a data.text.size property. The reason you
+might have the same property multiple times, is because it could have a different meaning.
+
+
For example:
+
+
data.raw.size: is the size in bytes
+
data.text.size: is the number of bytes in the text representation of the raw stream
+
+
If we want to check if either of these properties is not empty by using a $data matcher, we do:
The easiest way would be to only allow traces with the .pdf extension. Looking at the Hansken trace model (or a
+Hansken extraction), we can see that there’s a property file
+which contains a property extension.
+
So what would that look like in HQL-lite? Something like
+
file.extension=pdf
+
+
+
+
Warning
+
This of course only works if the file has the correct extension (note that matchers are case-sensitive).
+
+
So what do we do, if we also want to match pdf files that are (un)intentionally misnamed?
Looking at Wikipedia, we see that pdf has a couple of mime-types. In return looking at our extraction and the
+trace-model, we see both at data.raw.mimeType, with a further explanation in the Hansken trace model that
+the raw portion of the property is the data type of the datastream.
+
If we don’t know which datastream has the mimeType property beforehand, we could use the broad-scoped $data. matcher
+to look at every datastream.
+
So our matcher becomes:
+
file.extension=pdf OR
+(
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+)
+
Some pdf files can be huge, meaning that parsing them will need a lot of resources. Could we add a data size check to
+the matcher? According to the Hansken trace modeldata has a property size (similar to mimeType) that we
+could use for this.
+
+
Note
+
This is also a good way to check if a file is empty or not.
+
+
Let’s say our cutoff limit is 1 MB, meaning our matcher becomes:
+
0 < $data.size < 1000000 AND
+(
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+)
+
It is not uncommon to have some overlap between tools/plugins. For example:
+
+
PdfPlugin: a plugin that only supports pdf documents
+
DocumentPlugin: this plugin supports a lot of document types, including pdf.
+
+
So how would we prevent our plugin from processing a trace that has already been processed by the DocumentPlugin?
+
The easiest solution would be to check if a certain property has already been set. Meaning, that if both plugins set
+the foo.bar property, we check if said property has already been set.
+
So we only process the trace if foo.bar is empty, meaning our matcher becomes:
+
foo.bar!=* AND
+0 < $data.size < 1000000 AND
+(
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+)
+
It is also not uncommon to exclude certain paths from your plugin. These paths might contain invalid or encrypted files,
+for example.
+
So let’s say we want to exclude all files under in the /tmp/virus path. How do we go about it?
+
Again, we check our extraction/Hansken trace model, and we see that file.path looks promising.
+
So excluding /tmp/virus would look something like:
+
-file.path=/tmp/virus* AND
+foo.bar!=* AND
+0 < $data.size < 1000000 AND
+(
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+)
+
+
+
+
+
Match on specific datastream type, an anti-pattern
+
+
Warning
+
Matching on specific datastream types is an anti-pattern! It is not recommended to match on a datastream
+type. Instead one should match on other datastream properties, such as fileType, mimeType or mimeClass.
+The reason for this is explained in the paragraph below.
+
+
Using a matcher that is too loose or too tight can yield unexpected results. Usually one will match on properties
+of a datastream like fileType, mimeType or mimeClass as these are reliable properties that have been added by
+Hansken tools. Matching on a specific datastream says nothing about the type of file. For example a PDF file may be
+available in a raw as well as in a decrypted datastream. By matching on the datastream type one may exclude traces
+that were not intended to be excluded.
+Contrarily, note that matching on a datastream type may include more traces than you expected as well. For example,
+someone may think “Plugin A puts data on the plain datastream, so I’ll match on the plain datastream with Plugin B”,
+forgetting that plain may be used by other tools as well. In other words, there may be traces with that datastream
+type that you did not know of, potentially crashing your plugin. See Data streams for more information.
+
Now that you know why it is an anti-pattern, lets explain how it would be done (for those edge cases where it’s needed):
+Lets say we want our PdfPlugin to ONLY process raw datastreams.
+The best way to do this would be to match
+on $data.type:raw. Note that $data.type matches against the type of the current datastream, so in this case it
+matches only when the current datastream is of type raw.
+
An incorrect way to do it would be to replace $data. matcher(s) with data.raw.. This means the matcher
+will match whenever a trace has this datastream type, even if the current datastream type is different.
+Remember that the process method of an extraction plugin is always called once for each datastream on each trace.
+For example, lets say a trace has two datastreams, raw and text. The matcher would match for both the datastreams
+because the trace has a raw datastream (even though the current datastream type may be text). This results in the
+process method being called twice (for raw and for text), which may lead to other bugs if the developer doesn’t
+know this. For example, the second time the plugin may be trying to overwrite data on a trace which is prohibited.
+
So, using $data.type, our matcher would look like:
+
$data.type:raw AND
+-file.path=/tmp/virus* AND
+foo.bar!=* AND
+0 < $data.size < 1000000 AND
+(
+ file.extension=pdf OR
+ (
+ $data.mimeType=application\\/pdf OR
+ $data.mimeType=application\\/x-pdf
+ )
+)
+
In practice, only you as the plugin dev can answer this question.
+
Know that from the point of view of Hansken, we only care that the plugin:
+
+
Should not crash: If a matcher does not compile, then your plugin will not be available in Hansken. Tip: be sure
+to test your plugin with the test framework.
+
Should not be slow: Matching is designed to be extremely fast, but of course, if you make it too complex it can
+take longer than we want. In the example above, we calculated that 1 second extra for 1 million traces is 11 days of
+extra CPU time. Unlike processing, matching is done for every trace, in every extraction iteration, so be careful!
+
Should match on the bare minimum: Don’t go too far by matching 50 different criteria before allowing a trace to be
+processed. Note that a lot of (if not all) of these criteria depend on properties set by other tools, and you don’t
+really have any control on how these tools work.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/concepts/isolation.html b/0.9.16/dev/concepts/isolation.html
new file mode 100644
index 0000000..62416b7
--- /dev/null
+++ b/0.9.16/dev/concepts/isolation.html
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+ Plugin isolation — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Extraction plugins allows arbitrary code to be executed during a Hansken extraction. This code is executed inside the
+Hansken cluster. Extraction plugins are subjected
+to Hanskens design principles
+such as security, privacy and transparency. To ensure that plugins are compliant to these principles, each plugin will
+be executed in isolation. This page describes the isolation measures that are in place.
Plugins are only allowed to call a limited set of (Linux) system calls. This ensures that a plugin can be executed in a
+secure manner within the Hansken platform.
+
Hansken uses Kubernetes to run extraction plugins. The Kubernetes RuntimeDefault secure computing mode (seccomp) is
+enabled to provide a sane default of available system calls.
Extraction Plugins can be run in a Kubernetes cluster. This can be the same cluster Hansken run on, or another external
+cluster. For each plugin, a pod is created by Hansken. Each plugin will have 12 threads by default to process traces
+separately within one pod.
Hansken will create a Horizontal Pod Autoscaler (HPA) for each pod. HPA’s manage the number of replica’s of a pod
+based on metrics. The Extraction Plugins SDK provides two metrics to be set in the PluginInfo:
+
+
Observed CPU utilization
+
Observed memory usage
+
+
For more info on how to set these metrics follow these links:
If a pod reaches the CPU or memory usage provided with the PluginInfo, the HPA will increase the number of replicas for
+that plugin. Scaling down is done automatically. The maximum number of replicas per pod is specified within Hansken
+properties and can be adapted by an operator (needs restart).
This depends on the kubernetes cluster settings, the nodes it runs on, and of course the plugin itself. Monitor the
+number of replica’s and resource metrics while an extraction is running and adapt accordingly.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/concepts/plugin_naming_convention.html b/0.9.16/dev/concepts/plugin_naming_convention.html
new file mode 100644
index 0000000..c4d121e
--- /dev/null
+++ b/0.9.16/dev/concepts/plugin_naming_convention.html
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+ Plugin naming convention — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Each extraction plugin has a unique identifier. The identifier consists of three fields. These three fields combined
+form the plugin name.
+
The three fields of a plugin identifier are: domain, category, and name. The fields are described in more detail
+below.
+
domain
+The domain name of the organisation where the plugin is created. If an organisation has multiple domain names, the
+shortest name is preferred over the longer domain names. Examples: nfi.nl, politie.nl, fiod.nl, hansken.org.
+
category
+A type of action that the plugin performs. The category is a free text field, but the following table gives some
+recommendations.
+
+
+
Category
+
Description
+
+
+
+
extract
+
The plugin parses a clear data structure
+
+
carve
+
The plugin parses data fragments to reassemble traces in the absence of filesystem metadata
+
+
classify
+
The plugin categorizes a plugin based on its content, e.g. detecting money on traces of type picture
+
+
digest
+
The plugin digests data to compute a hash
+
+
ocr
+
The plugin applies ocr (optical character recognition) to read text on pictures or scanned documents
+
+
match
+
The plugin matches a trace against a database, and reports whether there was hit or miss, e.g. matching a trace to a well known files database
+
+
+
+
name
+The name of the plugin, or in the classic sense, a description detailing what the plugin processes. Note that the name
+can contain (forward) slashes.
The following table shows a list of plugin identifiers. The last column of the table shows the derived full plugin name.
+The derived full plugin name will be shown in Hansken.
+
+
+
Domain
+
Category
+
Name
+
Derived plugin name
+
Explanation
+
+
+
+
hansken.org
+
extract
+
archive
+
hansken.org/extract/archive
+
A plugin created by the Hansken development team that extracts traces from an arbitrary archive format
+
+
nfi.nl
+
extract
+
archive/zip
+
nfi.nl/extract/archive/zip
+
A plugin created by an NFI team that extracts traces from a specific archive format: zip
+
+
politie.nl
+
extract
+
archive/zip
+
politie.nl/extract/archive/zip
+
The same as the previous example, but now the plugin is created by a different organisation: politie.nl
+
+
hansken.org
+
carve
+
archive/zip
+
hansken.org/carve/archive/zip
+
A plugin that carves data to detect a specific archive format: zip
+
+
hansken.org
+
digest
+
sha256
+
hansken.org/digest/sha256
+
A plugin that digests data to compute a sha256 hash
+
+
hansken.org
+
ocr
+
tesseract
+
hansken.org/ocr/tesseract
+
A plugin that performs ocr using tesseract
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/concepts/plugin_types.html b/0.9.16/dev/concepts/plugin_types.html
new file mode 100644
index 0000000..22e2b10
--- /dev/null
+++ b/0.9.16/dev/concepts/plugin_types.html
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+ Extraction plugin types — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Meta Extraction Plugins can only process and produce trace properties without the need (or possibility) for processing
+actual trace data. This includes:
These plugins have two additional features that are not included with Standard Extraction Plugins:
+
+
a developer can choose to defer their execution
+
information about other traces can be obtained while processing the current extraction trace. Code examples can be
+found here: Java, Python.
+
+
Deferring execution
+A single Hansken image extraction consists of multiple iterations. Within every iteration, every Hansken tool and
+plugin is executed on matching traces, which produces new traces or modifies existing traces. If a tool can be executed
+another time because of these additions or modifications, another iteration is started.
+
A regular plugin is executed in the same iteration a trace is matched. Deferred plugins are executed in a different
+iteration; they are always deferred for at least one iteration. This is very useful when searching for traces, because
+you are certain the deferred plugin is executed after all other tools performed their modifications.
+
Sometimes, executing your plugin in the next iteration is not enough; it needs to be executed in a different iteration.
+This is why the SDK allows setting a deferredIterations parameter in the plugin info. After the plugin matches with a
+trace, it will be executed after deferredIterations. The execution can currently be deferred by a maximum of 20
+iterations and the default is 1.
+
Searching for traces
+This type of plugin can perform a search to look for extracted traces in the current image (default) or
+project (optional). This search is performed using a provided HQL query.
+A maximum of 50 traces is returned for a given search request.
+
+
Warning
+
Please note that HQL-Lite specific syntax such as the Data or DataType matchers is NOT supported.
These plugins have two additional features that are not included with standard Extraction Plugins:
+
+
a developer can choose to defer their execution
+
information about other traces can be obtained while processing the current extraction trace. Code example can be
+found here: Python.
+
+
Deferred Meta Extraction Plugins can only process and produce trace properties without the need (or possibility) for processing
+actual trace data. This includes:
+
+
Processing traces
+
Creating child-traces
+
Reading trace properties
+
Adding trace properties
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/concepts/test_framework.html b/0.9.16/dev/concepts/test_framework.html
new file mode 100644
index 0000000..d896065
--- /dev/null
+++ b/0.9.16/dev/concepts/test_framework.html
@@ -0,0 +1,428 @@
+
+
+
+
+
+
+
+
+ Test framework — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The SDK provides the FLITS Test Framework for integration testing. This allows us to test/validate the plugin input
+and output without having a running Hansken instance.
+
To use the test framework, three components are required:
The test data is independent of which programming language is used for the plugin (Java or Python). This section
+describes the setup of the test data, while the sections thereafter will link to the language specific documentation.
The inputs folder contains all traces that will be processed during the test. These ‘input traces’ are defined in
+files with the ‘.trace’ extension, using JSON. This JSON structure is explained in section
+Trace format. Each trace may have various data-streams. The data for each trace
+is put into separate files for each data-stream. The data-stream files need to have the same name as their corresponding
+trace file but differ in extension. They can have any extension, for example ‘raw’, ‘text’ or ‘jpeg’. Note that one
+input trace will always have one ‘.trace’ file, and can have none, one or many data files. Also note that if the
+plugin doesn’t match on any of the input files and there are no result files yet, the test will succeed.
+
+
Note
+
The test-framework uses the extension of the input test file(s) _(other than __.trace__)_ as type of the
+current data-stream.
+
+
The expected results (which are also traces) are stored in a separate results folder next to the inputs folder. The
+file names in the results folder correspond to the file names in the inputs folder. Note that the name of the plugin
+is added between the file basename and the file extension. This can be useful if one maintains a single test input and
+output test datasets for multiple extraction plugins.
+
+
Note
+
It is possible to let the test framework regenerate the results files automatically. See the
+Java and Python sections on testing on how to do this. If no files are
+being generated, check if the plugin matcher is actually matching the input files.
+
+
The test runner will invoke the extraction plugin for each input trace. The test runner collects the plugin output and
+compares it against the trace defined in the results folder. If there is a mismatch, the test runner will fail with an
+exit code 1. If all tests pass the test runner will finish with exit code 0.
+
Given the files in the example above, the test runner will invoke the extraction plugin three times:
+
+
+
Input
+
Result
+
+
+
+
example1.trace with data streamexample1.raw
+
example1.raw.PluginName.trace
+
+
example1.trace with data streamexample1.text
+
example1.text.PluginName.trace
+
+
example2.trace with data streamexample2.text
+
example2.raw.PluginName.trace
+
+
+
+
+
+
Test data structure for deferred extraction plugins
+
Deferred extration plugins have the unique ability to search traces with a query.
+The input test data should be extended to contain the results of searches done by deferred extraction plugins. These
+search traces are stored in separate folders that follow the naming format ‘{deferred trace name}/searchtraces/’. Below
+is an example test data directory structure for a deferred extraction plugin that searches for
+a deferredExampleSearch.trace:
The plugin will try to match on all traces in the input folder, including traces used for search results (
+of deferred extraction plugins). This means that it is impossible to search on traces that match the same deferred
+extraction plugin, as it would create an infinite loop.
+
+
Given the files in the example above, the test runner will invoke the extraction plugin one time:
+
+
+
Input
+
Result
+
+
+
+
deferredExample.trace with data stream deferredExample.raw
+
deferredExample.raw.DeferredPluginName.trace
+
+
+
+
+
Warning
+
The search query should be written in HQL, as that is how Hansken will interpret it. However, the test
+framework interprets the query using its HQL-lite interpreter. Therefore, not all queries will be supported.
Input and result traces both stored in a JSON structure. There is however a slight difference between the two: The
+result trace may store additional values that are purely there for testing purposes. The input format will first be
+discussed, followed by the result format.
Input traces start with a trace key, which contains a mapping of properties. The property names are split in a
+dictionary structure. The example below shows a serialized trace with six properties: data.raw.mimeClass and the five
+data types that are currently supported by the test-framework.
+
The data key defines the data-streams of the trace. When adding a data-stream make sure you also
+add the corresponding input data file, as described above.
The extraction plugin SDK and the test framework have no knowledge of the
+trace model. This means that when properties are used that don’t
+comply with the trace model, this will not cause the test to fail, but it will fail when running your plugin in Hansken.
The result traces have the same format as the input traces, namely a trace key which contains the full input trace
+with all its properties. However, the result traces may have two additional keys children and data (which are
+explained in-depth below). These are added for testing purposes. If the plugin adds child traces
+or writes data transformations to Hansken, this would normally not reflect on the JSON of the
+trace. However, the test framework adds these to the result JSON structure to be able to test them.
+
Consequently, result traces are stored in a JSON structure that may consist of up to three parts, namely the always
+present trace and the occasional children and data:
+
+
trace: The key trace contains a mapping of its properties, in exactly the same way as is done for input traces.
+
children: :ref:Childtraces<childtraces> that have been created by the plugin during the test are stored under a
+reserved field children, which is a list of traces. The example trace below contains a child trace with a property
+name.
+
data: Data transformations that have been created by the plugin during the test are
+stored under a reserved field data. For each data-stream type there is a descriptor field describing the data
+transformation in a JSON format. The example trace has a ranged data transformation for the raw data-stream. Note that
+this data is entirely different from the data key that may be present inside the trace!
Some scenarios may throw exceptions and this can be part of your tests too. For example, an input file that has the
+wrong format can be part of your integration tests. When an exception occurs during the test, it will be written to the
+result file. This can be deliberately used to test exceptions. However, it is often impractical to match against a full
+exception. For example, the row numbers in the exception are very much prone to change due to circumstances irrelevant
+to the case being tested. Therefore, the testframework provides some options to match only on those parts of result
+files that are relevant to the test.
+
The following sections will explain these partial result matchers using the following example exception:
+
{
+"class":"org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
+"message":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris faucibus varius sodales."
+}
+
The containsInOrder partial result matcher requires a list of strings as a parameter. The result will be valid if
+every string in the list can be found in that same order in the actual result.
+
{
+"class":"org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
+"message.containsInOrder":[
+"Lorem ipsum dolor sit amet,",
+"consectetur adipiscing elit.",
+"Mauris faucibus varius sodales."
+]
+}
+
The Test Framework itself is built in Java. When building extraction plugins with Java, it can be incorporated in your
+unit tests, as shown in Using the Test Framework in Java.
The Python SDK also uses the Java based Test Framework. This is done by providing a wrapper to make calls to an included
+Test Framework ‘jar’ file. See Advanced use of the Test Framework in Python for documentation
+and examples on how to use FLITS for testing your Python plugin.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/concepts/traces.html b/0.9.16/dev/concepts/traces.html
new file mode 100644
index 0000000..304bc9a
--- /dev/null
+++ b/0.9.16/dev/concepts/traces.html
@@ -0,0 +1,340 @@
+
+
+
+
+
+
+
+
+ Traces & Trace model — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Traces are structured data objects produced by tools/plugins during an extraction. A trace represents a piece of
+information found in an evidence file.
+
The following figure shows the main elements of a trace. Each element is described in more detail in the following
+paragraphs.
A trace has properties that describe the information of it by means of a property value. Trace properties are grouped by
+a trace type. A trace can have multiple types.
+
All types and properties that can be set are defined in the Hansken trace model.
+
An example of a type is document, which could have the properties application and createdOn. The trace will have a
+type document, and can the following properties with values:
A trace has several intrinsic properties. These are properties that are not related to a trace type. The intrinsic
+properties available to extraction plugins are:
+
+
id: a unique identifier of the trace, generated when a trace is created
+
name: a name given to a trace when it is created
+
path: a logical path of the trace, of which the elements are the names of traces from the root trace until this
+trace
Typically, a trace represents a piece of data found in an evidence file. This data is part of the trace and available as
+a data stream. A trace can have multiple data streams. Each data stream has a type. Data streams can also have
+properties that apply to the data stream itself. The data stream properties are modeled as properties of the trace, in
+the following pattern:
+data.<datastreamtype>.propertyname (where <datastreamtype> is substituted by the actual type of the data stream).
+
The set of data stream types and data stream properties is fixed. All allowed types and properties are defined in the
+Hansken trace model (see data).
+
An important data stream property is the fileType property. This property contains a textual description of the
+detected file type for the data stream. An example of a fileType is ‘Adobe Pdf’. The fileType is a good candidate
+to use in a extraction plugin ‘matcher’. This fileType is detected by Hansken using file type heuristics, which are
+primarily based on the data stream bytes itself, and secondarily on other metadata such as a file extension.
+(N.B. The fileType is detected in Hansken by the extraction tool FireFli.) For more information on how datastream
+properties can be used for matching, see here.
+
Note that not all traces have data streams. In these cases it is a trace of meta-data derived from another trace.
+
Usually, each trace with data has a data stream of type raw. This data stream contains the bytes of the traces as they
+were found when the trace was created. In some occasions, the raw data can be represented in a different form before it
+can be processed further, for example if the data can be decoded or decrypted. Hansken tools and plugins can decode
+the raw data stream to a standard UTF-8 data stream, or can decrypt the data if a decryption key is present. Hansken
+tools and extraction plugins can store the new data at the new trace in a new data stream. This new data stream has a
+different type than the raw type.
A trace can have child traces. For example, a trace of type archive can have children, where each child is a trace
+that represents an entry in the archive.
+
With an extraction plugin it is possible to create child traces for a trace that is being processed. New properties,
+data streams, and other child traces can be set on the new child traces. When a child trace is created, the plugin
+should provide a name for the child trace. The id of the child trace is generated, in the following
+form: parenttraceid-childnumber. For example, if the parent has an id 0-0-0-0-0:0-9, the first child gets the
+id 0-0-0-0-0:0-9-1, the second child gets the id 0-0-0-0-0:0-9-2, and so on.
+
Note that a trace does not have (direct) access to its parent trace.
A vector is a data type that can be used to store points in n-dimensional space as an array of floating point values.
+Once indexed, the vectors can then be used in a gui or other client to search for traces that have a nearby vectors.
+For example, it is possible to use a neural network that provides embeddings of human faces as vectors. Once indexed,
+the vectors can then be used to find pictures with similar faces. To do this, the search rest api can be used to
+sort by the euclidean- or manhattan distance, or cosine similarity to a given vector.
A Tracelet is a bundle of property values that belong to a single type. It is a property on a trace that can have
+multiple properties itself, making it a list of key/value pairs. The API doesn’t specify the cardinality, but the
+implementation is limited to cardinality ‘Few’. In Hansken these are called FVT’s (Few Valued Types).
+
+
Note
+
MVT’s (Many Valued Types) are currently not supported in the SDK and will be added in a future release.
+
+
An example of a tracelet is the prediction property, which describes a category or class a trace belongs to. It is
+possible for a trace to have multiple predictions. Therefore prediction is a tracelet. Other examples of
+tracelets are identity and collection.
All traces in Hansken are based on a specific version of the trace model, and must comply to that version of the trace
+model. This is a nested data structure composed of origins, categories, types and properties.
+
All non-inrinsic trace properties are optional and are grouped by type. These types are defined under the trace
+model section ‘categories’. Every category has a list of allowed types. When a trace is identified as being a
+document, it will get this set of predefined document properties. Trace types can have different origins. The
+possible origins are defined in the trace model section ‘origins’. An example of this is the processed types that are
+always generated by the system during an extraction.
+
The details of the current trace model can be retrieved using the /tracemodel
+REST call on the Gatekeeper endpoint of Hansken, or check the Hansken Documentation on the trace model.
The extraction plugin SDK has no knowledge of the trace model
+
+
The Extraction Plugins SDK has no knowledge of the trace model at this time. It is however possible to create new traces
+with plugins. If any newly created Traces don’t comply to the model, Hansken will not accept them and mark the plugin
+execution as failed. The Extraction Plugins SDK and the provided Test Framework don’t check this.
+Please make sure to use the right naming when creating new Traces, as provided by the trace model.
+
If an erroneous trace property is set, Hansken will show an error. The error can be found in the Hansken Expert UI
+interface by double-clicking on the trace. Then the trace details screen will be opened and the error will be displayed
+as follows:
+
+
This error describes that a property does not exist in the trace model. To get more information about the error, the
+extraction log can be viewed. In the extraction log you have to search
+for java.lang.IllegalArgumentException:nosuchtype to find out which property is not supported by the trace model.
+
In the example extraction log below, the property this_property_does_not_exist could not be found 681 times.
You will first need git access to the Hansken developer community. Here you can find started guides and examples. If you
+have no access yet, you can get access by following the next steps:
After you have created your account, you should request access to the ”
+Hansken Community” group . Do this by contacting your organisations Hansken business owner. If you don’t know who to
+contact as your business owner, please read the Contact page.
The SDK contains an API and tools to write a Hansken extraction plugin in Java or Python. The Java API can be used to
+develop extraction plugins in JVM-compatible languages, such as Scala and Kotlin.
Probably not. It takes time and effort to create a proper SDK. If you think there is a good use case to support
+language foobar, and there is gRPC support, feel free to contact us. We can discuss the options to add support for
+Hansken extraction plugins with foobar.
+
Under the hood, extraction plugins use gRPC to communicate with Hansken. In theory, all programming languages that have
+a gRPC implementation can be used to write Hansken extraction plugins.
+
+
+
Can I reuse or modify the Extraction Plugins SDK?
+
The SDK is distributed under the Apache 2.0 License, see the LICENSE file in the SDK for more details.
We are doing everything to make sure Extraction Plugins are as safe as possible, however note that the Extraction Plugin
+SDK is still in beta. Use it at your own risk. For more information on security see Isolation.
+
+
+
Can my Extraction Plugin be embedded into Hansken for performance reasons?
+
Embedding an Extraction Plugin into Hansken requires access to the Hansken source code. If you have access to the source
+code then please ../contact us for assistance. Please note that embedded Extraction Plugins are not officially
+supported.
+
If you do not have access to the Hansken source code, then please contact your own Business Owner, and ask them to
+contact the Hansken Team.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/introduction.html b/0.9.16/dev/introduction.html
new file mode 100644
index 0000000..0203a2b
--- /dev/null
+++ b/0.9.16/dev/introduction.html
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+ Introduction — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The Extraction Plugin Software Development Kit can be used to develop a Hansken
+extraction plugin.
+
Hansken is designed to give access to and insight in digital data and traces originating from seized and demanded
+digital material. One aspect of the Hansken platform is the extraction engine (extraction framework). The extraction
+engine contains digital forensics knowledge, which is used to find traces in digital material. With extraction plugins
+case investigators can add new digital forensics knowledge to the extraction framework. In this way, Hansken is enabled
+to understand new digital formats, and thus is able to find new types of traces in the seized and demanded material.
+
Examples of digital forensics knowledge that can be added to Hansken with extraction plugins:
+
+
new file formats (e.g. a new crypto currency wallet)
+
combine traces to find new information (e.g. use a windows registry entry required to read a file from disk)
+
apply algorithms on traces (e.g. speech to text from audio files)
+
+
The primary goals of this SDK are:
+
+
to make it as easy as possible to add new digital forensics knowledge to Hansken.
+
be able to share digital forensics knowledge with other Hansken community members
This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that
+are important to plugin developers. For a full list of changes per version, please refer to the general
+changelog.
This release introduces a new parameter bulkMode to the PluginInfo. This can be used for
+lightweight plugins which have to process a lot of data (either a lot of traces or a small
+number of traces with large data streams). These plugins will run inside the worker pod for streaming extractions,
+and will therefore be able to process data more efficiently.
This release introduces the deferred meta extraction plugin. This plugin type can defer their execution and
+processes a trace only with its metadata, without processing its data and accesses traces using the searcher.
+This makes it possible to use deferred plugins in combination with traces without data.
+Note: Hansken will support these types of plugins from v47.34.0.
+
This release introduces new parameters for the TraceSearcher, start and sort.
+This allows the searcher can get a certain range of traces in a specific order.
+Note: Hansken will support these types of plugins from v47.34.0.
The trace property imageId is renamed to image. This is to be in line with the Hansken REST API and Python API.
+When updating your plugin, please update your calls trace.get("imageId") to trace.get("image").
+
#774
+By default, deferred extraction plugin searches are now scoped to the image
+of the trace that is currently being processed. Optionally, a project-wide
+search can be done by passing an optional scope argument.
+
@Override
+publicvoidprocess(finalTracetrace,finalExtractionContextcontext,finalTraceSearchersearcher){
+// only search for traces inside the same image as the trace that is being processed
+finalSearchResultresult=searcher.search("file.extension=asc",10);
+finalSearchResultresult=searcher.search("file.extension=asc",10,TraceSearcher.SearchScope.IMAGE);
+
+// search for all traces inside the same project as the trace that is being processed
+finalSearchResultresult=searcher.search("file.extension=asc",10,TraceSearcher.SearchScope.PROJECT);
+}
+
+
+
+
Support trace properties of type List<Integer>, List<Double>, and List<Float>.
+This enables you to write multiple offsets and confidence scores in tracelets of type prediction.
Escaping the / character in matchers is optional.
+This simplifies and aims for better HQL and HQL-Lite compatability.
+See for more information and examples the HQL-Lite syntax documentation.
Hansken returns file.path properties as a String property, instead of a List<String>.
+Example: trace.get("file.path") now returns "/dev/null", this was ["dev","null"].
A plugin can now write multiple data streams to a single trace concurrently,
+e.g. write both decrypted and ocr at the same time. See the “Adding data to a trace” code snippets for
+general examples on adding data to a trace.
It is highly recommended to upgrade your plugin to this new version.
+See the migration steps below.
+
+
+
Extraction plugin container images are now labeled with PluginInfo. This
+allows Hansken to efficiently load extraction plugins.
+
By default, extraction plugin version is managed in the plugin’s pom.xml.
+The .pluginVersion(..) can be removed from the PluginInfo builder.
+
Migration steps from earlier versions – for plugins that use the Java
+extraction plugin SuperPOM:
+
+
Update the SDK version in your pom.xml
+
If you come from a version prior to 0.4.0, or if you use a plugin name
+instead of a plugin id in your pluginInfo(), switch to the plugin id style
+(read instructions for version 0.4.0)
+
Set your plugin version in your project’s pom.xml, and remove the
+following from your PluginInfo.Builder:
+
.pluginVersion(...)
+
+
+
+
Update your build scripts to build your plugin (Docker) container image.
+You should build your plugin container image with the following command:
+
mvnpackagedocker:build`
+
+
+
This will generate a plugin image:
+
+
The extraction plugin is added to your local image registry
+(dockerimages),
+
The image name is extraction-plugin/PLUGINID, e.g.
+extraction-plugin/nfi.nl/extract/chat/whatsapp,
+
The image is tagged with two tags: latest, and your plugin version.
+
+
Nb. If Docker is not available in your environment, podman can be used
+as an alternative. See packaging for more
+details.
When writing input search traces for tests, it is no longer required to explicitly set an id property.
+These are automatically generated when executing tests.
A new convenience method id(String,String,String) is added to the PluginInfo builder. This removes some
+boilerplate code when setting the pluginId. More details on the plugin naming conventions can be found at the
+Plugin naming convention section.
+
PluginInfo.builderFor(this)
+.id("nfi.nl","extract","TestPlugin")// new style
+.id(newPluginId("nfi.nl","extract","TestPlugin"))// old style, but also works
+...
+
It is now possible to specify maximum system resources in the PluginInfo. To run a plugin with 0.5 cpu (= 0.5
+vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to PluginInfo:
Extraction Plugins are now identified with a PluginInfo.PluginId containing a domain, category and name. The
+method PluginInfo.name(pluginName) has been replaced by PluginInfo.id(newPluginId(domain,category,name). More
+details on the plugin naming conventions can be found at the Plugin naming convention section.
+
PluginInfo.name() is now deprecated (but will still work for backwards compatibility).
+
A new license field PluginInfo.license has also been added in this release.
+
The following example creates a PluginInfo for a plugin with the name TestPlugin, licensed under
+the ApacheLicense2.0 license:
Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations
+describe how data can be obtained from a source.
+
An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in
+the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of
+the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of
+space is saved.
+
Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data
+transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in
+a bytearray.
+
The following example sets a new datastream with dataType html on a trace, by setting a ranged data transformation:
The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged
+data transformation with two ranges:
This is an API breaking change. Plugins created with an earlier version of the extraction plugin SDK are
+not compatible with Hansken that uses 0.2.0 or later.
+
+
+
Introduced a new extraction plugin type DeferredExtractioPlugin. Deferred Extraction plugins can be run at a
+different extraction stage. This type of plugin also allows accessing other traces using the searcher.
+
The class ExtractionContext has been renamed to DataContext. The new name DataContext represents the class
+contents better. Plugins have to update matching import statements and the type in ExtractionPlugin.process()
+implementation in the same way. This change has no functional side effects.
To debug a plugin locally, it is recommended to start the plugin via the IDE by running the integration test. This has
+the advantage that breakpoints can easily be put in the code instead of printing log statements, for example.
If the Docker image is not built, run the following command to build the Docker image:
+
mvnpackagedocker:build
+
+
+
+
+
Run the Docker image with specific Java tool options
+
In Java, the remote debug functionality is not enabled by default. To enable the remote debug functionality, the
+following environments variable must be set in the Docker container:
This environment variable allows the debugger to connect to the debuggee (application being debugged). To start the
+Docker image with the JAVA_TOOL_OPTIONS environment variable, the following command can be used:
The next step is to attach the debugger to the debuggee.
+For Intellij, the instructions are clearly described on the following
+page: Tutorial: Remote debug
The logging of the extraction plugin is displayed in the console after running the dockerrun command. In addition,
+the logging is also displayed in the IntelliJ console while debugging.
An HQL query can be debugged by overriding the isVerboseLoggingEnabled() method of the ExtractionPluginFlits class.
+The example below shows an example of an embedded FLITS test with verbose logging enabled.
The following output will then be displayed in the console:
+
HQL match found for:
+$data.type=jpg
+With trace:
+dataType=jpg
+types={file, data}
+properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0}
+
+
+
If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query
+is $data.mimeType=image/jpg (slash not escaped). This query will produce an error like the one shown below.
The error is only shown in the generated trace, so to find out the ParseException override
+the regenerate() method from Flits and then let this method return true.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/java/javadoc.html b/0.9.16/dev/java/javadoc.html
new file mode 100644
index 0000000..09a4bf4
--- /dev/null
+++ b/0.9.16/dev/java/javadoc.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+ Javadoc — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Extraction plugins are packaged as OCI images (also known as Docker images).
+The OCI images are labeled with the PluginInfo.
+To automate packaging of a Java plugin and labeling the OCI image, the Extraction Plugin SuperPom has been configured to automate this for you.
+
If your project uses the Extraction Plugin SuperPom (see Prerequisites), Packaging an extraction plugin is handled by Maven.
+To package your plugin into a container image, the following command can be used:
+
mvnpackagedocker:build
+
+
+
This will generate a plugin image:
+
+
The extraction plugin is added to your local image registry
+(dockerimages),
+
The image name is extraction-plugin/PLUGINID, e.g.
+extraction-plugin/nfi.nl/extract/chat/whatsapp,
+
The image is labeled with two tags: latest, and your plugin version.
+
+
It is possible to apply extra arguments to the docker command as described here.
+For example, to specify a proxy, use the following command:
Once your plugin is packaged, it can be published or ‘uploaded’ to Hansken.
+See “Upload the plugin to Hansken” for instructions.
+
Note: if your build environment does not have Docker available, you can use
+podman as an alternative. Install podman on your machine
+or build agent, and run the following commands before invoking the
+mvnpackagedocker:build command:
Docker for packaging and publishing plugins
+(or use a Docker alternative such as podman)
+
Maven (recommended, build automation tool)
+
+
Required dependencies:
+
+
All required project dependencies to build extraction plugins are published on the public Maven Central, under org.hansken.plugin.extraction:plugin-super-pom.
+For maven based extraction plugins, the following pom.xml snippet can be used as basis of a plugin:
In Java, InputStream is a common type to pass data to another class or method. The SDK provides a simple utility to
+use a RandomAccessData as InputStream.
Next we can create an InputStream from the RandomAccessData as shown in the following snippet. Note that the
+InputStream is created using a try-with-resources-statement. This ensures that the InputStream is correctly closed
+when the InputStream is no longer required.
+
RandomAccessDatatraceData=...;
+try(InputStreamasInputStream=RandomAccessDatas.asInputStream(traceData)){
+// use the InputStream here
+}
+
+
+
Notes:
+
+
the created InputStream is not thread-safe,
+
the created InputStream changes state in the provided RandomAccessData
+(e.g. when data is read, the position updated in both the InputStreamand
+the RandomAccessData instances),
+
for more details on the implementation of the InputStream, refer to the RandomAccessDataInputStream JavaDoc.
In the following Java example, a “classification” tracelet is added to a trace. The tracelet consists
+of a list of four properties, namely “class”, “confidence”, “modelName” and “modelVersion”.
The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged
+data transformation with two ranges:
It is not always possible to create a transormation for the data that has to be
+added to a trace. For example the data is a result of a computation, and not
+a direct subset of another data stream..
+
The following examples show how to creates a new data stream of dataType raw on a trace.
+
In case all data is stored in a byte[], we can add the byte array to the data stream with:
In the PluginInfo you can specify maximum system resource metrics for a plugin. These are used for scaling the
+number of pods as described here. To run a plugin with 0.5 cpu (=
+0.5 vCPU/Core/hyperthread), 1 gb memory and 10 (concurrent) cpu workers (threads), for example, the following configuration can be added to PluginInfo:
Using a deferred plugin requires inheriting the DeferredExtractionPlugin base class. This allows access to
+a TraceSearcher object in the process function to search for traces.
a HQL query (note: this is the traditional HQL query, and not the matchers HQL-lite variant),
+
the maximum number of traces the return (currently hard-limited to a maximum of 50 traces),
+
(optional) a scope, which can be either TraceSearcher.SearchScope.IMAGE (default), or TraceSearcher.SearchScope.PROJECT.
+When set to IMAGE, the searcher will only search for traces within the same image as the trace that is being processed.
+
+
The traces contained in the SearchResult are returned as a stream.
The PluginInfo contains a parameter bulkMode. This can be used for lightweight plugins which have to process a lot
+of data (either a lot of traces with data or a small number of traces with large data streams). For streaming
+extractions, these plugins will run inside the worker pod, and will therefore be able to process data more efficiently.
+
WARNING: The plugin should be lightweight. This means that it should not use a lot of resources like CPU or memory,
+because this will limit the resources of the worker pod, and therefore Hansken will not be able to start enough workers
+to do extractions.
+
Creating a plugin with bulk mode enabled can be done by setting the parameter in PluginInfo as follows:
The logging is provided by Log4j 2 with a SLF4J binding. The Log4j 2 SLF4J binding allows applications coded to the
+SLF4J API to use Log4j 2 as the implementation.
Here is an example illustrating how to log something with SLF4J. It begins by getting a logger with the name “LOG”. This
+logger is in turn used to log the message I'mloggingavariable1234!.
+
importorg.slf4j.Logger;
+importorg.slf4j.LoggerFactory;
+
+publicclassExample{
+privatestaticfinalLoggerLOG=LoggerFactory.getLogger(Example.class);
+
+publicvoidexample(){
+finalintaNumber=1234;
+// logs to console: I'm logging a variable 1234!
+LOG.info("I'm logging a variable {}!",aNumber);
+}
+}
+
It’s easy to change the logging format with a file called log4j2.xml. If desired, this file must be in the resources
+folder, for example src/main/resources/log4j2.xml
More information about customizing the logging can be found here.
+
+
+
Note
+
The default logger is pre-configured to log INFO to STDOUT (see the configuration above)
+
+
+
Note
+
Log4j 2 supports various logging formats, including xml, yaml, json, properties, etc.
+Currently, only the xml format is supported.
+
+
+
Note
+
Contact your Hansken administrator for more information on where to find logs for your Hansken environment.
+
+
+
+
+
[EXPERIMENTAL FEATURE] Adding previews to a trace
+
+
Warning
+
This is an experimental feature, which might change or get removed in future releases.
+
+
Example:
+
publicclassExamplePluginextendsExtractionPlugin{
+@Override
+publicPluginInfopluginInfo();
+
+@Override
+publicvoidprocess(finalTracetrace,finalDataContextcontext){
+finalbyte[]previewData;
+// set the preview data for the image/png MIME-type
+trace.set("preview.image/png",previewData);
+trace.set("preview.image/png",previewData);
+}
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/java/testing.html b/0.9.16/dev/java/testing.html
new file mode 100644
index 0000000..9b14e69
--- /dev/null
+++ b/0.9.16/dev/java/testing.html
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+ Using the Test Framework in Java — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
There are ways of integration testing a plugin with the Test Framework:
+
+
Embedded testing: Here the plugin is run directly from a JUnit test without using the gRPC layer.
+
Remote testing: Here the test will start an ExtractionPluginServer that will serve the plugin. All communication
+is between server and plugin is done using gRPC.
+
+
See below for an example of each way of testing.
+The Extraction Plugin Examples contains many
+more examples.
import staticnl.minvenj.nfi.flits.util.FlitsUtil.srcPath;
+
+importjava.nio.file.Path;
+
+importorg.hansken.plugin.extraction.api.ExtractionPlugin;
+importorg.hansken.plugin.extraction.test.EmbeddedExtractionPluginFlits;
+
+/**
+ * An integration test for MyPlugin.
+ */
+classMyPluginITextendsEmbeddedExtractionPluginFlits{
+
+@Override
+protectedExtractionPluginpluginToTest(){
+// MyPlugin is a class implementing the ExtractionPlugin interface,
+// with pluginInfo() and process() methods.
+returnnewMyPlugin();
+}
+
+@Override
+publicPathtestPath(){
+// Provide the folder containing input files. For examples, see
+// https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+returnsrcPath("integration/inputs");
+}
+
+@Override
+publicPathresultPath(){
+// Provide the folder containing result files. For examples, see
+// https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+returnsrcPath("integration/results");
+}
+
+@Override
+publicbooleanregenerate(){
+// Returning false means the test will fail if the result files don't
+// match the outcome of the=++ test. Returning true means the test create
+// new result files .
+returnfalse;
+}
+}
+
Note that the following example serves the plugin by using ExtractionServer.
+
import staticnl.minvenj.nfi.flits.util.FlitsUtil.srcPath;
+
+importjava.nio.file.Path;
+
+importorg.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginClient;
+importorg.hansken.plugin.extraction.runtime.grpc.server.ExtractionPluginServer;
+importorg.hansken.plugin.extraction.test.plugins.DataTransformationsPlugin;
+importorg.junit.jupiter.api.AfterAll;
+importorg.junit.jupiter.api.BeforeAll;
+
+publicclassRemoteTransformationPluginFlitsITextendsRemoteExtractionPluginFlits{
+
+privatestaticExtractionPluginServer_server;
+privatestaticExtractionPluginClient_client;
+
+@BeforeAll
+publicstaticvoidinit()throwsException{
+finalintport=8999;
+
+// Serve MyPlugin.
+// MyPlugin is a class implementing the ExtractionPlugin interface, with PluginInfo and Process methods.
+_server=ExtractionPluginServer.serve(port,MyPlugin::new);
+
+// Create an ExtractionPluginClient
+_client=newExtractionPluginClient("localhost",_server.getListeningPort());
+}
+
+@AfterAll
+publicstaticvoiddestruct(){
+// At the end of the test, make sure the server and client are closed.
+if(_server!=null){
+_server.close();
+}
+if(_client!=null){
+_client.close();
+}
+}
+
+@Override
+publicPathtestPath(){
+// Provide the folder containing input files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+returnsrcPath("integration/inputs");
+}
+
+@Override
+publicPathresultPath(){
+// Provide the folder containing result files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
+returnsrcPath("integration/results");
+}
+
+@Override
+protectedExtractionPluginClientpluginToTest(){
+// For Remote testing, the test won't talk directly to the plugin, but to the client.
+// The client will use gRPC to communicate with the served plugin.
+return_client;
+}
+
+@Override
+publicbooleanregenerate(){
+// Returning false means the test will fail if the result files don't match the outcome of the test.
+// Returning true means the test create new result files.
+returnfalse;
+}
+}
+
+
+
+
Note
+
Note that with a RemoteTransformationPluginFlitsIT it is possible to start a docker image of a plugin and
+run remote tests against it using your own testdata. To do this, simply remove all _server code and manually start
+your plugin in a docker container. Then run the test against the docker container by setting the correct url and
+port, presumably newExtractionPluginClient("localhost",8999).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/python.html b/0.9.16/dev/python.html
new file mode 100644
index 0000000..b90df5d
--- /dev/null
+++ b/0.9.16/dev/python.html
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+ Python — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Update or add metadata properties for this .ExtractionTraceBuilder.
+
Can be used to update the name of the Trace represented by this builder,
+if not already set.
+
+
Parameters:
+
+
key_or_updates – either a str (the metadata property to be
+updated) or a mapping supplying both keys and values to be updated
+
value – the value to update metadata property key to (used
+only when key_or_updates is a str, an exception will be thrown
+if key_or_updates is a mapping)
+
data – a dict mapping data type / stream name to bytes to be
+added to the trace
Update or add metadata properties for this .ExtractionTrace.
+
+
Parameters:
+
+
key_or_updates – either a str (the metadata property to be
+updated) or a mapping supplying both keys and values to be updated
+
value – the value to update metadata property key to (used
+only when key_or_updates is a str, an exception will be thrown
+if key_or_updates is a mapping)
+
data – a dict mapping data type / stream name to bytes to be
+added to the trace
CPU resources are measured in cpu units. One cpu is equivalent to 1 vCPU/Core for cloud providers and 1 hyperthread
+on bare-metal Intel processors. Also, fractional requests are allowed. A plugin that asks 0.5 CPU uses half as
+much CPU as one that asks for 1 CPU.
TransformerLabel contains information about a transformer method that a plugin provides.
+
It is mainly used for storing the properties (name, arguments, return type) of a transformer in PluginInfo objects.
+Unlike the Transformer class it does not contain the actual function reference to the transformer itself.
Search for indexed traces in Hansken using provided query returning at most count results.
+
+
Parameters:
+
+
query – HQL-query used for searching
+
start – Starting index of traces to return
+
count – Maximum number of traces to return
+
scope – Select search scope: ‘image’ to search only search for other traces within the image of the trace
+that is being processed, or ‘project’ to search in the scope of the full project (either Scope-
+enum value can be used, or the str-values directly).
+
sort – The fields and directions to sort on
+
+
+
Returns:
+
SearchResult containing found traces
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/python/api/hansken_extraction_plugin.api.tracelet.html b/0.9.16/dev/python/api/hansken_extraction_plugin.api.tracelet.html
new file mode 100644
index 0000000..7f1625f
--- /dev/null
+++ b/0.9.16/dev/python/api/hansken_extraction_plugin.api.tracelet.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+ hansken_extraction_plugin.api.tracelet — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
A tracelet contains the values of a single fvt (Few Valued Type).
+
A few valued type is a trace property type that is a collection of tracelets. A trace can contain multiple few
+valued types containing one or more tracelets. For example, the trace.identity` type may look like this:
This module contains the Transformer class that holds the function reference of the transformer.
+
Instances of this class are constructed by BaseExtractionPlugin when retrieving transformers dynamically.
+It also validates whether the method to which @transformer is applied adheres to the requirements of a transformer.
Generate a TransformerLabel given the transformer method. TransformerLabels are used in PluginInfo objects.
+
Unlike Transformers TransformerLabels can be serialized and sent to a client that wishes to call a transformer.
+The specific Python types are converted to the generic types that are used in the Hansken trace model.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/python/api_changelog.html b/0.9.16/dev/python/api_changelog.html
new file mode 100644
index 0000000..1357221
--- /dev/null
+++ b/0.9.16/dev/python/api_changelog.html
@@ -0,0 +1,678 @@
+
+
+
+
+
+
+
+
+ Python API Changelog — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that
+are important to plugin developers. For a full list of changes per version, please refer to the general
+changelog.
This release introduces a new parameter bulk_mode to the PluginInfo. This can be used for
+lightweight plugins which have to process a lot of data (either a lot of traces or a small
+number of traces with large data streams). These plugins will run inside the worker pod for streaming extractions,
+and will therefore be able to process data more efficiently.
This release introduces new parameters for the trace_searcher, start and sort.
+This allows the searcher can get a certain range of traces in a specific order.
+Note: Hansken will support these types of plugins from v47.34.0.
+
This release allows the user to search for more than 50 traces using the GrpcTraceSearcher. By specifying a count
+greater than 50, results will be retrieved in batches of 50 (or less) until the desired count is achieved.
+Setting the count to None (or omitting it) allows the GrpcTraceSearcher to retrieve all available traces.
+This functionality is implemented in a buffered manner and is defined within BatchedSearchResult,
+which replaces the now-removed GrpcTraceResult. Note: the search results are still limited by Elasticsearch
+so no more than 100.000 results can be obtained.
This release introduces the deferred meta extraction plugin. This plugin type can defer their execution and
+processes a trace only with its metadata, without processing its data and accesses traces using the searcher.
+This makes it possible to use deferred plugins in combination with traces without data.
+Note: Hansken will support these types of plugins from v47.34.0.
This release introduces a simple flow-control mechanism that fixes connectionRST errors that can occur when a
+plugin produces traces too fast. Plugins built with this version are not backwards compatible.
+
Python plugins now support transformers, remote methods which can be executed using the Hansken REST API. More information can be found in the docs.
+
The build_plugin and label_plugin utilities prematurely shut down containers if the building and labeling process takes too long causing the process for slow containers to fail. If your plugin takes a long time to start, you may want to increase the timeout before the script stops trying to connect and aborts the process of building the plugin. This can be done using the new optional --timeout argument. The default is set to 30 seconds.
+
The optional image name argument of build_plugin is changed to a flag. Build scripts can be updated using --target-nameDOCKER_IMAGE_NAME.
⚠️ This release is deprecated, please upgrade to 0.8.3
+
The build_plugin utility has been updated and the deprecation status has been removed.
+As with label_plugin, build_plugin now no longer requires a full (virtual) environment
+with all plugin dependencies and resources. This will greatly reduce build times for plugins with
+big dependencies and/or large models.
+
The first argument of the command (a pointer to your plugin.py file) has been removed.
+Please do not forget to remove the first argument of build_plugin in your tox.ini or other build tooling.
The default read-buffer of trace.open('rb') as been changed from 1 Megabyte to 6 Megabyte to reduce overhead while data reading.
+
The data stream writer of trace.open('wb') is now buffered as well. This means that multiple small writes will be flushed after every 6 Megabytes of data has been written (or when the writer is closed).
+
The read-buffer or write-buffer size can be overridden by the user, by passing the buffer_size= argument to trace.open():
+
withtrace.open('rb',buffer_size=1024*1024):# set a 1 Megabyte buffer size
+ pass
+
+withtrace.open('wb',buffer_size=1024*1024*12):# set a 12 Megabyte buffer size
+ pass
+
+withtrace.open('wb',buffer_size=1):# a buffer_size of 1 effectively disables the buffer:
+ pass# each write will be flushed to Hansken directly
+
+
+
+
It is now possible to write str values to trace.open(..). To do so, pass mode='w' as additional argument.
+By default, it is assumed that the written text is ‘utf-8’ encoded. The default can be overwritten by using the 'encoding=' argument.
+
In a future Hansken update, Hansken will set the correct data-stream properties for your text stream (mimeType, mimeClass, and fileType).
+
Example use cases are:
+
+
write picture-to-text (OCR) data to a trace
+
write translations to a trace
+
write audio-to-text (audio transcriptions) to a trace
+
write the results of a JSON dump, e.g.: json.dump(['your','data'],text_writer)
+
+
Examples in code:
+
withtrace.open(data_type='raw',mode='w',encoding='utf-8')astext_writer:
+ text_writer.write('hello.world')# write strings directly to it
+ json.dump({'hello':'world'},text_writer)# or pass the writer to json.dump
+
The trace property imageId is renamed to image. This is to be in line with the Hansken REST API and Python API.
+When updating your plugin, please update your calls trace.get('imageId') to trace.get('image').
+
#774
+By default, deferred extraction plugin searches are now scoped to the image
+of the trace that is currently being processed. Optionally, a project-wide
+search can be done by passing an optional scope argument.
+
defprocess(trace,data_context,searcher):
+ # only search for traces inside the same image as the trace that is being processed
+ searcher.search('*')
+ searcher.search('*',scope='image')# explicit alternative, using a str
+ searcher.search('*',scope=SearchScope.image)# explicit alternative, using the SearchScope enum
+
+ # only search for traces inside the same image as the trace that is being processed
+ searcher.search('*',scope='project')
+ searcher.search('*',scope=SearchScope.project)
+
+
+
+
Support trace properties of type list[float]. This enables you to write
+multiple offsets and confidence scores in tracelets of type prediction.
+
For example:
+
trace.add_tracelet('prediction',{
+ 'modelName':'my_cat_detector',
+ 'modelVersion':'0.0.BETA',
+ 'type':'classification',
+ 'label':'cat',
+
+ # the best score
+ 'offset':3.0,
+ 'confidence':0.4,
+
+ # all scores
+ 'offsets':[0.0,3.0,6.0,9.0],
+ 'confidences':[0.1,0.4,0.03,0.09],
+})
+
This version introduces a new docker image build utility label_plugin.
+This utility will eventually replace build_plugin. build_plugin is now deprecated.
+
label_plugin is a utility to add labels to an extraction plugin image. Labeling a plugin is required for
+Hansken to detect extraction plugins in a plugin image registry.
+
To label a plugin, first build the plugin image with docker build;
+for example by using one of the following commands:
Next, run the label_plugin utility to label the build plugin container:
+
label_pluginmy_plugin
+
+
+
The result of label_plugin is a plugin image that can be uploaded to Hansken.
+
label_plugin is preferred over build_plugin, as it does not require a full (virtual) environment
+with all plugin dependencies and resources. This is especially preferred when the plugin uses (big)
+data models or (external) dependencies.
Escaping the / character in matchers is optional.
+This simplifies and aims for better HQL and HQL-Lite compatability.
+See for more information and examples the HQL-Lite syntax documentation.
Hansken returns file.path properties (outside the scope of matchers) as a String property,
+instead of a list of strings.
+Example: trace.get('file.path') now returns '/dev/null', this was ['dev','null'].
+
Improved plugin loading when using serve_plugin and build_plugin:
+import statements now work for modules (python files) that are located the same directory structure of a plugin.
+
A plugin can now stream data to a trace using trace.open(mode='wb').
+This removes the limit on the size of data that could be written.
+See also the python code snippet.
The docker image build script build_plugin has been updated to allow for extension of the docker command.
+This can be especially handy for specifying a proxy. You should build your plugin container image with the following
+command:
This is an API breaking change.
+Upgrading your plugin to this version will require code changes.
+Plugins built with previous versions of the SDK from 0.3.0 will still work with Hansken.
+
+
+
Warning
+
It is strongly recommended to upgrade your plugins to this new version because it significantly improves
+the start-up time of Hansken. See the migration steps below.
+
+
This release contains both build pipeline changes and API changes.
+Please read all changes carefully.
Extraction plugin container images are now labeled with PluginInfo. This
+allows Hansken to efficiently load extraction plugins.
+Migration steps from earlier versions:
+
+
Update the SDK version in your setup.py / requirements.txt
+
If you come from a version prior to 0.4.0, or if you use a plugin name
+instead of a plugin id in your pluginInfo(), switch to the plugin id style
+(read instructions for version 0.4.0)
+
Update your build scripts to build your plugin (Docker) container image.
+Be sure to have the Extraction Plugins SDK installed.
+Then, you should build your plugin container image with the following command:
# no need for a builder, declare resources by direct instantiation
+returnPluginInfo(
+ ...,
+ resources=PluginResources(maximum_cpu=2.0,maximum_memory=2048)
+)
+# or, as before, specify just on resource
+returnPluginInfo(
+ ...,
+ resources=PluginResources(maximum_memory=4096)
+)
+
Simplify tracelet properties by making the tracelet type prefix optional.
+
# using a Tracelet object
+trace.add_tracelet(Tracelet("prediction",{
+ "type":"example",
+ "confidence":0.8
+}))
+# or without a Tracelet object
+trace.add_tracelet("identity",{"name":"John Doe","status":"online"})
+
When writing input search traces for tests, it is no longer required to explicitly set an id property.
+These are automatically generated when executing tests.
More $data matchers are supported in Hansken.py plugin runner. Before this improvement it was only possible to match
+on $data.type. Now it is also possible to match for example on $data.mimeType and $data.mimeClass. The $data
+matcher should still be at the end of the query as before.
It is now possible to specify maximum system resources in the PluginInfo. To run a plugin with 0.5 cpu (= 0.5
+vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to PluginInfo:
Extraction Plugins are now identified with a PluginInfo.PluginId containing a domain, category and name. The
+method PluginInfo.name(pluginName) has been replaced by PluginInfo.id(newPluginId(domain,category,name). More
+details on the plugin naming conventions can be found at the Plugin naming convention section.
+
PluginInfo.name() is now deprecated (but will still work for backwards compatibility).
+
A new license field PluginInfo.license has also been added in this release.
+
The following example creates a PluginInfo for a plugin with the name TestPlugin, licensed under
+the ApacheLicense2.0 license:
Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations
+describe how data can be obtained from a source.
+
An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in
+the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of
+the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of
+space is saved.
+
Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data
+transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in
+a bytearray.
+
The following example sets a new datastream with dataType html on a trace, by setting a ranged data transformation:
The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged
+data transformation with two ranges:
This is an API breaking change.
+Plugins created with an earlier version of the extraction plugin
+SDK are not compatible with Hansken that uses 0.2.0 or later.
+
+
+
Introduced a new extraction plugin type api.extraction_plugin.DeferredExtractioPlugin.
+Deferred Extraction plugins can be run at a different extraction stage.
+This type of plugin also allows accessing other traces using the searcher.
+
The class api.extraction_context.ExtractionContext has been renamed to api.data_context.DataContext.
+The new name DataContext represents the class contents better.
+Plugins have to update matching import statements accordingly.
+Plugins should also update the named argument context to data_context of the plugin process() method.
+This change has no functional changes.
Moved api.author.Author to api.plugin_info.Author, and moved api.maturity_level.MaturityLevel
+to api.plugin_info.MaturityLevel
+This is a more pythonic way of grouping of classes into modules. This change has no functional side effects.
+
Plugins have to update matching import statements accordingly.
Removed DataContext.get_first_bytes() from the public API.
+
Removed api.extraction_trace.validate_update_arguments(..) from the public API. This method is still invoked
+implicitly when setting trace properties.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/python/debugging.html b/0.9.16/dev/python/debugging.html
new file mode 100644
index 0000000..0bf4737
--- /dev/null
+++ b/0.9.16/dev/python/debugging.html
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+
+
+ How to debug an Extraction Plugin — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
To debug a plugin locally, it is recommended to start the plugin via the IDE. This has the advantage that breakpoints
+can easily be put in the code instead of printing log statements, for example. To start a plugin locally, a piece of
+code must be added, see Testing for more information.
Debugging an extraction plugin via docker is a bit trickier. In order to debug in Python, a debugger must be added to
+the extraction plugin. There are several debug modules for Python available, but one debug module that works well with
+Visual Studio Code is debugpy. This package is developed by Microsoft
+specifically for use in Visual Studio Code with Python.
+
+
Note
+
debugpy implements the Debug Adapter Protocol (DAP), which is a standardised way for development tools to
+communicate with debuggers.
+
+
Using debugpy with Docker containers requires 4 distinct steps:
+
+
Install debugpy
+
Configuring debugpy in Python
+
Build a docker image
+
Configuring the connection to the Docker container
At the beginning of your script, import debugpy, and call debugpy.listen() to start the debug adapter, passing
+a (host,port) tuple as the first argument. Use the debugpy.wait_for_client() function to block program execution
+until the client is attached.
+
importdebugpy
+
+debugpy.listen(("0.0.0.0",5678))
+debugpy.wait_for_client()# blocks execution until client is attached
+
+# your extraction plugin code
+
If the Docker image is not built, first build the image as described
+here.
+
+
+
Configuring the connection to the Docker container
+
debugpy is now set up to accept connections inside a Docker container. To connect to debugpy in the docker
+container, port 5678 must be published. To make a port available to services outside of Docker, use the –publish or -p
+flag. This creates a firewall rule which maps a container port to a port on the Docker host to the outside world.
+
To run the extraction plugin with the published port the following command can be used:
+
dockerrun-p5678:5678your_extraction_plugin_name
+
+
+
The next step is to configure Visual Studio Code. A launch.json file must be created in order for Visual Studio Code
+to connect to the extraction plugin in Docker. This minimal launch.json example below tells the debugger to attach
+to localhost on port 5678.
The logging of the extraction plugin is displayed in the console after running the dockerrun command. In addition,
+the logging is also displayed in the Visual Studio Code console while debugging.
An HQL query can be debugged by running the test framework with the --verbose option enabled. The found HQL matches
+will then be displayed in the console. To test a plugin in python with the --verbose option enabled use the following
+command:
The following output will then be displayed in the console:
+
HQL match found for:
+$data.type=jpg
+With trace:
+dataType=jpg
+types={file, data}
+properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0}
+
+
+
If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query
+is $data.mimeType=image/jpg (slash not escaped). This query will produce an error like the one shown below.
The following section describes how to set up a fully working development environment for extraction plugins with Python.
+This is written for those who are not comfortable setting up a working build environment.
+This is optional; advanced users may choose a different development environment setup, and can skip this section completely.
+
If you fail to set up a development environment, feel free to ask for help at our Discord channel.
In order to be able to develop Hansken Extraction Plugins in Python on Ubuntu, the following build tools need be installed on your system: python, pip, tox, Java, Docker.
+
+
Python, pip, tox, Java
+To install, run the following commands in your terminal:
You can check whether all versions are installed correctly by running the following commands (and validate the output):
+
python--version
+# should return version 3.10.15 or higher
+
+pip3--version
+# should return version 20.0.2 or higher
+
+tox--version
+# should return version 3.11.2 or higher
+
+java-version
+# should return version 11.0.4 or higher
+
+
+
+
+
If the above software is installed, you can continue with “Install Docker”,
+or if you don’t want to install Docker, continue with “Install your IDE: Pycharm”.
In order to be able to develop Hansken Extraction Plugins in Python on Windows, follow the next steps:
+
+
For verification of the installation of each program we will use commands on the command prompt.
+This can be opened by clicking the Windows Start button and typing:
+
cmd
+
+
+
Then hit Enter . This will open the command prompt where you can enter the commands given in the following steps.
+
+
Python 3.10 or higher & pip
+Download the installer from python.org/Downloads (click the yellow “Download Python” button)
+and run it to install Python and pip.
+Pip is the standard package manager for Python.
+It allows you to install and manage additional packages that are not part of the Python standard library, like the Extraction Plugins SDK.
+
Be sure to select the option “Add python to PATH”.
+
When the installation is complete, verify the installation by checking the Python and pip versions:
+
python--version
+# should return your downloaded Python version (>3.10.4)
+
+pip3--version
+# should return 20.2.3 or higher
+
+
+
+
tox
+Tox is used to automate and standardize testing in Python. Use Pip to install Tox with this command:
+
pipinstalltox
+
+
+
Verify that Tox is installed by running:
+
tox--version
+# should return 3.23.0 or higher
+
+
+
+
Java JDK 11.0.4 (or higher)
+Java JDK 11.0.4 or higher is needed to run the test framework of the Extraction Plugins SDK.
+This enables you to test without actually deploying the plugin in Hansken.
+Installing Java on Windows can be done in many different way, but for now we can not recommend one method.
+Please have a look at Install the Microsoft Build of OpenJDK for more details.
+
java-version
+# should return 11.0.4 or higher
+
+
+
N.b. Make sure to set the environment variable in case the installed JDK is not detected by the system.
+You can follow the below-mentioned steps to set the environment variable:
+
+
Click the Windows start button
+
Type “advanced system settings”
+
Hit enter
+
Now click on Environment Variables, select Path under System Variables section and click on Edit. We need to add the path of installed JDK to system Path.
+
Click on New Button and add the path to installed JDK bin which is C:javajava-11jdk-11.0.4bin in our case.
+
Press OK Button 3 times to close all the windows. This sets the JDK 11 on system environment variables to access the same from the console.
+
+
+
+
If the above software is installed, you can continue with “Install Docker”,
+or if you don’t want to install Docker, continue with “Install your IDE: Pycharm”.
Installing Docker is a bit more complicated. The Docker website describes the installation instructions in detail.
+Please follow the instructions on docs.docker.gom/get-docker/ to install docker.
+
+
To check you have Docker installed correctly, run
+
docker--version
+# should return version 20.10.17 or higher
+
+
+
+
+
Note: if you run Docker inside a managed network, you might also need to configure proxies and/or certificates.
+Please contact your system administrator if you need help with this.
Download an extraction plugin template (empty plugin)
+
You can download an extraction plugin template.
+This is an empty plugin, from which you can rapidly start your plugin development.
+
+
The template is hosted on GitHub here.
+You can download a zip with the template from here. The below screenshot shows where the download button is located:
+
+
+
+
+
+
Import the Extraction Plugins Skeleton in PyCharm
+
+
First unzip the skeleton plugin downloaded from the previous step.
+
Next, start PyCharm.
+
When PyCharm starts, choose “Open” and select the folder where you placed the Extraction Plugin Skeleton.
+
+
+
The following popup will appear, click OK .
+
+
+
The Extraction Plugins Skeleton is now loaded in PyCharm, which should look as follows:
+
+
+
Be sure to give the README.md a read when you are done with the Prerequisites.
To verify that your system has been setup correctly, you can run the test suite in the Extraction Plugin Skeleton:
+
+
First, press alt-F12 at the same time to open a terminal in PyCharm in the project root folder.
+
To run the tests of the Skeleton, run this command in the terminal from the root folder:
+
tox
+
+
+
The first time running tox may take a few minutes! Please be patient 😊
+
Tox will install all required plugin dependencies, and start your tests.
+N.b. The plugin template demonstrates how to build a plugin with tox.
+Some plugin developers choose a different tool than tox.
+
+
If your system has been set up correctly, the output should end with a summary like this:
+
py38:commandssucceeded
+congratulations:)
+
+
+
This means the setup is finished. You now have everything installed to start coding your own plugin!
Hansken.py is a Python client to Hansken’s REST API, developed and maintained by the Netherlands Forensic Institute.
+
With Hansken.py, you can run your Python plugin on a project on your Hansken installation that has already been
+extracted. This way of running your plugin is useful during your plugin development. It is not required to upload your
+plugin to Hansken and start an extraction, making the development cycle faster. However, please note that this is only
+useful during the development stage of your plugin, as the execution of your plugin will be much slower compared to
+running the plugin during a Hansken extraction.
+
+
Note
+
The execution of your plugin will be much slower in Hansken.py compared to running the plugin during a Hansken
+extraction.
+
+
+
How to run python extraction plugins standalone with Hansken.py
+
Running python extraction plugins standalone with Hansken.py is easy. It is just one command. This section explains how
+to setup this command for your specific environment.
Before you can enter the command that runs your extraction plugin with Hansken.py, you need to find three values:
+
+
HANSKEN_PROJECT_ID project id on which you want to run your plugin
+
YOUR_GATEKEEPER_URL the URL to the Hansken gatekeeper
+
YOUR_KEYSTORE_URL the URL to your keystore
+
+
The correct values of these variables can be found in the Expert UI. Go to the search-page of your project in the
+ExpertUI. Next to the search-bar hit the button “Save query”.
+
+
A new dialog shows up. At the bottom of this dialog, you will find your gatekeeper and keystore urls as well as the
+project id.
Next, open a terminal in the project root folder of your plugin and enter the following command. It will run your
+extraction plugin with Hansken.py in Hansken. Replace the three variables with their respective values.
If your command runs well, you might be prompted for your username and password. There will be some output (note that
+the output may vary depending on your system setup and project content):
+
[2021-03-16 12:59:45.344248+0000] INFO: hansken.auth: selected IDP ID (...) with SOAP endpoint (...)
+[2021-03-16 12:59:45.344450+0000] WARNING: hansken.auth: IDP url known, user+pass auth required but no username supplied
+username []: testaccount
+[2021-03-16 12:59:48.423245+0000] INFO: hansken.auth: user acknowledged environment username or supplied custom username: testaccount
+password for user testaccount:
+[2021-03-16 12:59:53.799668+0000] INFO: hansken.auth: identity provider url and user+pass provided or known, using Keycloak SAML with Basic auth
+[2021-03-16 12:59:53.805538+0000] INFO: hansken_extraction_plugin.runtime.extraction_plugin_runner: PluginRunner is running plugin class Plugin
+[2021-03-16 12:59:53.859299+0000] INFO: hansken.auth: posting SAML request with authorization for user testaccount to IDP endpoint (...)
+[2021-03-16 12:59:54.240290+0000] INFO: plugin.extraction_plugin: processing trace 54197e67-8135-40c3-93f1-3d73a5552693
+[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace OCRimage
+[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace (...)
+
+
+
Note that the arguments -v and -l- are passed to enable logging. To find out what other options can be passed to
+this command, please have a look at the hansken.py documentation, or simply run the following command:
Extraction plugins are packaged as OCI images (also known as Docker images).
+The OCI images are labeled with the PluginInfo.
+To automate packaging of a Python plugin and labeling the OCI image,
+the Extraction Plugin SDK comes with three utility applications: label_plugin, build_plugin and build_plugin_ci.
+
To package a plugin, make sure that the Extraction Plugins SDK is installed, as well as Docker.
+Next build and label your plugin as described in the following sections.
+
To verify that the image has been built, use the following command to view all local images:
+
dockerimages
+
+
+
Once your plugin is packaged and labelled, it can be published or ‘uploaded’ to Hansken.
+See “Upload the plugin to Hansken” for instructions.
label_plugin is a utility to add labels to an extraction plugin image.
+To label a plugin, first build the plugin image with docker build;
+for example by using one of the following commands:
Next, run the label_plugin utility to label the build plugin container:
+
label_pluginmy_plugin
+
+
+
This utility will briefly start your plugin using Docker, and requests the PluginInfo from the plugin.
+The information from the PluginInfo will be added as labels to the plugin image.
+The result of label_plugin is a plugin image that can be published to a docker/OCI image registry.
The build_plugin extends label_plugin by also taking care of the docker build command.
+Use this as an one-liner to both build and label your plugin image.
+
To build your plugin container image you can use the following command:
The extraction plugin is added to your local image registry (dockerimages),
+
Note that the variables $http_proxy and $https_proxy are put in quotes, this is needed in case they contain
+spaces,
+
The image is tagged with two tags: latest, and your plugin version.
+
+
Arguments:
+
+
DOCKER_FILE_DIRECTORY: Path to the directory containing the Dockerfile of the plugin.
+
(Optional) DOCKER\_IMAGE\_NAME: Name of the docker image without tag. Note that docker image names cannot start with
+a period or dash. If it starts with a dash, it will be interpreted as an additional docker argument (see
+DOCKER_ARGS). If no name is given the name defaults to extraction-plugin/PLUGINID, e.g.
+extraction-plugin/nfi.nl/extract/chat/whatsapp.
+
(Optional) DOCKER\_ARGS: Additional arguments for the docker command, which can be as many arguments as you like.
The build_plugin_ci performs the same tasks as build_plugin except that it uses a different approach for labeling
+the plugin. Not all CI/CD pipelines allow docker containers to be started and connected to, something which
+build_plugin relies on to label the plugin correctly using the plugin info specified in the plugin. build_plugin_ci
+uses another approach which exports the image and then parses the plugin info from this image. This way a container
+does not have to be started. Therefore the advantage of build_plugin_ci is that it can more reliably build plugins on
+CI systems. The downside of this approach is that it is slower than build_plugin utility. It is therefore adviced to
+use build_plugin when developing locally to speed up the development process and to use build_plugin_ci when
+building plugins in CI/CD pipelines.
To build your plugin container image using build_plugin_ci you have to add the following line to your Containerfile
+after you have copied your plugin to the image. build_plugin_ci requires this to find the plugin information.
Other than the options provided by build_pluginbuild_plugin_ci also provides a --build_agent flag which allows a
+user to choose between different build agents to build and label their plugin. Currently only docker, podman and
+buildah are supported. For example:
The extraction plugin is added to your local image registry (dockerimages),
+
Note that the variables $http_proxy and $https_proxy are put in quotes, this is needed in case they contain
+spaces,
+
The image is tagged with two tags: latest, and your plugin version.
+
+
Arguments:
+
+
DOCKER_FILE_DIRECTORY: Path to the directory containing the Dockerfile of the plugin.
+
(Optional) DOCKER\_IMAGE\_NAME: Name of the docker image without tag. Note that docker image names cannot start with
+a period or dash. If it starts with a dash, it will be interpreted as an additional docker argument (see
+DOCKER_ARGS). If no name is given the name defaults to extraction-plugin/PLUGINID, e.g.
+extraction-plugin/nfi.nl/extract/chat/whatsapp.
+
(Optional) BUILD\_AGENT: The build agent that should be used to build the plugin. Either docker, podman or buildah.
+Docker is the default.
+
(Optional) BUILD\_AGENT\_ARGS: Additional arguments for the buildagent command, which can be as many arguments as
+you like.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0.9.16/dev/python/prerequisites.html b/0.9.16/dev/python/prerequisites.html
new file mode 100644
index 0000000..f6a56d1
--- /dev/null
+++ b/0.9.16/dev/python/prerequisites.html
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+ Prerequisites — Hansken Extraction Plugins for plugin developers 0.9.16
+ documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Use update
+to add trace types and their properties to an
+ExtractionTrace.
+Example:
+
defprocess(self,trace,data_context):
+ # get the name of the file
+ file_name=trace.get('file.name')
+ # set the chat application property on the trace
+ trace.update('chatConversation.application',f'DemoApp {file_name}')
+
+
+
All types and properties that can be set are defined in the Hansken trace model.
If the information, which must be added as a property, does not match any of the existing properties of Hansken trace
+model, use the category “misc” (miscellaneous). When part of the category “misc”, any name can be given to a property.
+The values of miscellaneous properties are expected to be of data-type string. Example:
+
defprocess(self,trace,data_context):
+ trace.update({
+ 'file.misc.notes':'Some additional notes about the file trace.',
+ 'file.misc.anyName':'Even more notes.'
+ })
+
In the following Python example, a “prediction” tracelet is added to a trace. The tracelet consists
+of a list of four properties, namely “class”, “confidence”, “modelName” and “modelVersion”.
The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged
+data transformation with two ranges:
It is not always possible to create a transformation for the data that has to be
+added to a trace. For example, if the data is a result of a computation, and not
+a direct subset of another data stream..
+
The following snippet shows how to create a new data stream of dataType raw on a trace from a blob stored in bytes:
Streaming data does not work with the Hansken.py runner because Hansken.py does not support it. It does
+work when running your plugin in Hansken and in the test framework.
+
+
When dealing with large quantities of data, it is possible to keep the memory usage
+of the plugin within manageable limits by streaming the data from the plugin to Hansken in smaller chunks.
+To do this, use the withtrace.open(data_type=...,mode='wb') syntax. Here are some examples:
To write str values directly, use mode w (or wt).
+By default, it is assumed that the written text is ‘utf-8’ encoded. The default encoding can be overwritten by using the 'encoding=' argument.
+
(In a future Hansken update) Hansken will set the correct data-stream properties for your text stream (mimeType, mimeClass, and fileType).
+
withtrace.open(data_type='raw',mode='w',encoding='utf-8')astext_writer:
+ text_writer.write('hello.world')# write strings directly to the writer
+ json.dump({'hello':'world'},text_writer)# or pass the writer to json.dump
+
+
+
It is recommended to pass utf-8 explictly as encoding.
It is possible to specify system resources hints in the PluginInfo. To run a plugin with at least 0.5 cpu (= 0.5
+vCPU/Core/hyperthread), 1 gb memory and 10 (concurrent) cpu workers (threads), for example, the following configuration can be added to PluginInfo:
a HQL query (note: this is the traditional HQL query, and not the matchers HQL-lite variant),
+
(optional) the maximum number of traces to return (currently hard-limited to a maximum of 50 traces),
+
(optional) a scope, which can be either image, or project. When set to image, the searcher will only search for traces
+within the same image as the trace that is being processed.
+
+
The returned SearchResult
+should be closed, for example by using with. The resulting search result is an iterable, which will be exhausted when
+no more traces are available. The search result allows taking one or more traces by calling :py:
+meth:take<hansken_extraction_plugin.api.search_result.SearchResult.take> or
+takeone.
+
+
Note
+
The command trace.open(datastream_type) will fail on search result traces that do not originate from the
+same image (evidence item) as the trace that is being processed.
Implementing a deferred meta extraction plugin requires inheriting the
+DeferredMetaExtractionPlugin
+base class. This plugin is not able to call the trace.open() method since the actual trace data is not available to this plugin.
+Also matching on data type will not work for this plugin since this plugin only works for meta traces
The PluginInfo contains a parameter bulk_mode. This can be used for lightweight plugins which have to process a lot
+of data (either a lot of traces with data or a small number of traces with large data streams). For streaming
+extractions, these plugins will run inside the worker pod, and will therefore be able to process data more efficiently.
+
WARNING: The plugin should be lightweight. This means that it should not use a lot of resources like CPU or memory,
+because this will limit the resources of the worker pod, and therefore Hansken will not be able to start enough workers
+to do extractions.
+
Creating a plugin with bulk mode enabled can be done by setting the parameter to True in the PluginInfo as follows:
We use Logbook to log messages in Python. Logbook is a logging system for Python that replaces the standard library’s
+logging module.
+
To enable logging in your plugin, add the following to the top of your plugin code:
+
fromlogbookimportLogger
+
+log=Logger(__name__)
+
+
+
From there on the logging is pretty straight forward:
+
log.info(f'Logging a variable: {my_variable}')
+
+
+
The default log level is WARNING. There are two ways to set the logging level. You can use the -v (or -vv or -vvv) option of serve_plugin.py to increase the log level. This is typically done in the plugin Dockerfile. Another option is to use an environment variable, LOG_LEVEL. Available levels are WARNING, NOTICE, INFO and DEBUG. The environment variable overrides the option.
+
+
Warning
+
Be careful with logging sensitive information.
+
+
+
Note
+
Contact your Hansken administrator for more information on where to find logs for your Hansken environment.
+
+
+
+
[EXPERIMENTAL FEATURE] Adding previews to a trace
+
+
Warning
+
This is an experimental feature, which might change or get removed in future releases.
By default, the build scripts as described in the Getting Started section will automatically run
+tests. The appropriate commands have been added to the tox.ini directly. This section gives a little more detail on the
+test commands and options.
+
One can simply create unit tests for a plugin directly. However, we also provide a test-framework for testing them over
+gRPC. The test-framework serves a running instance of a Python plugin, and feeds it input files and compares the results
+against an expected result set.
+
Note that the test-framework is implemented in Java, hence the Java 11 requirement. A jar file is included in the Python
+SDK which is called from a Python wrapper.
The test runner is a script called test_plugin which is available in the SDK.
+
To get started, cd into the directory of the plugin you want to test and run:
+
test_plugin--standaloneplugin/chat_plugin.py
+
+
+
Note that the argument provided to the option --standalone in this example is the relative path to the plugin py
+file which is to be tested. This test accepts input files from the directory testdata/input and compares the results
+to the result files in found in testdata/results. Use the optional argument --regenerate to regenerate the expected
+results for the test when needed.
+
This standalone test is also used by the tox.ini file to validate the plugin. Simply calling tox should be enough to
+install all dependencies and run the test.
+
In case the plugin was defined in a module that is to be imported by the interpreter, it’s also possible to run this
+command by referencing either the module that defines the plugin or the plugin directly:
+
test_plugin--standaloneplugin_module
+# or, assuming the plugin class is called ChatPlugin
+test_plugin--standaloneplugin_module:ChatPlugin
+
Replace the ‘extraction-plugin-examples-chat’ with the docker image you want to test. Run the following command to see
+which docker images are available:
The third option for testing is a manually started plugin. Start the plugin service in a terminal by executing:
+
serve_plugin-vvvplugin/my_plugin.py
+
+
+
This will spin up the chat plugin at port 8999. In this example the argument is also a path to the plugin’s .py file.
+The same alternative reference to the plugin shown with test_plugin above applies here.
To start the extraction plugin from code, create a __main__ method which calls the _test_validate_standalone
+method of the test framework (see the example below). This method causes the extraction plugin to be started and
+supplied with data by the FLITS test framework. In this way the test can be started from the IDE, which has the
+advantage that it is easier to debug.
+
fromhansken_extraction_plugin.test_framework.test_pluginimport_test_validate_standalone
+fromhansken_extraction_plugin.api.extraction_pluginimportExtractionPlugin
+
+
+classPluginToTest(ExtractionPlugin):
+
+ defplugin_info(self):
+ # return plugin info
+ pass
+
+ defprocess(self,trace,data_context):
+ # process the data/trace here
+ pass
+
+
+if__name__=='__main__':
+ _test_validate_standalone(PluginToTest,'testdata/input','testdata/result',False)
+
The sdk includes a script to test transformer functions. This script will start an extraction plugin, executes a single transformer function with the provided arguments and stops the plugin. Try it with:
Transformers are methods inside a plugin that can be called remotely at any moment.
+This allows for live plugin execution independent of extraction.
+Examples on how transformers could be used:
+
+
For searching images using text (i.e. a purple car).
+
For translating text in traces so that an investigator can read the text in their preferred language in a UI.
Transformers can be implemented in extraction plugins. Using the Hansken REST API, calls can be made to a
+specific plugin’s transformer by specifying the transformer one wishes to call as well as its arguments.
+Hansken can automatically discover transformers before plugins are actually started.
+Once it has received a request for invoking a transformer it can choose to start the plugin’s Docker container if it
+is not already started and send the request once it is up and running.
A transformer can be easily defined by using the transformer decorator. By creating a method inside your plugin and
+decorating it with the transformer decorator it will automatically be made available to be remotely called.
+
fromhansken_extraction_plugin.api.extraction_pluginimportExtractionPlugin
+fromhansken_extraction_plugin.decorators.transformerimporttransformer
+classPlugin(ExtractionPlugin):
+
+ defplugin_info(self):
+ ...
+
+ defprocess(self,trace,data_context):
+ ...
+
+ @transformer
+ deftranslate_text(self,text:str,language:str)->str:
+ # To implement: Translate the text here.
+ return"Translated text"
+
However, there are some limitations on which methods can be turned into a decorator method:
+
+
Transformers may only be defined on methods of a class that derives (indirectly) from BaseExtractionPlugin.
+
+
Note: ExtractionPlugin derives from BaseExtractionPlugin and is therefore allowed.
+
+
+
Transformer may not be static methods.
+
All parameters and the return type must be annotated with type hints (except the self parameter).
+
Parameters may not be positional-only or contain variable parameters like *args or **kwarg.
+
Parameters and return types may only be of the following (returning None is not supported):
+
+
bool
+
int
+
float
+
str
+
bytes
+
bytearray
+
datetime.datetime (The datetime will be converted to unix time and then converted to datetime with the zone “UTC”. So the zone id is always(“UTC”))
+
hansken.util.GeographicLocation
+
hansken.util.Vector
+
typing.Sequence
+
typing.Mapping
+
+
+
+
Upon starting a plugin every method decorated with the transformer decorator will be automatically validated to see if they adhere to these requirements. An exception will be thrown when a method does not adhere to all of these requirements.
If at some point a transformer wishes to signal to the caller of the transformer that something has gone wrong
+it can simply throw a suitable exception. Any exceptions being thrown will automatically be propagated to the client
+calling the transformer by wrapping the exception in a gRPC exception. The stack trace will be provided as well for
+debugging purposes.
This REST Call calls a specified transformer method on a given tool/plugin with supplied arguments.
+This endpoint allows a transformer to be executed on demand, outside an extraction. The transformer call can be used to transform user input to an output. A tool/plugin can have multiple transformer methods.
+To use /tools/transformers REST CALL you need to know the following (see extractions tool or use the rest call/tools to get the information below):
+
+
The name of the plugin/tool
+
The transformer name
+
The names of the parameters (arguments)
+
+
Example JSON request body for the transformer above:
If you use the Java or Python extraction plugin SDK, you don’t have
+to worry about these specifications. The Java and Python SDKs makes
+sure your plugin is compiled and packaged conform to the extraction
+plugin specifications.
+
+
This page describes the specifications that define an extraction plugin.
+The spec contains two major parts: a plugin protocol, and the plugin packaging
+method.
+
This specification applies to plugins that are not embedded within Hansken,
+but to plugins that developed and distributed outside the scope of the Hansken
+platform development.
An extraction plugin is a process that implements a GRPC
+service ExtractionPluginService. The service defines a protocol that is
+used to allow communication between Hansken and an extraction plugin. The
+GRPC and protocol definitions can be found in the extraction plugin source
+code, under the folder grpc.
+
+
Note
+
The source code of the Extraction Plugin is currently not available
+outside the scope of the Hansken core development teams. If you are
+interested in the GRPC definitions, please Contact the
+Hansken development team.
An extraction plugin is packaged as a container image – conform the open
+container initiative image spec.
+An extraction plugin can be
+
The ENTRYPOINT of the container image should be a process that starts a GRPC
+server that implements the plugin protocol. The GRPC protocol should run on port
+8999 by default. The plugin should always observe the PLUGIN_PORT environment
+variable, and run on that port if set.
+
The container image should be labeled with the plugin info.
+The plugin info returned by the plugin plugin-info call and container labels
+are required to match. If not, Hansken will not accept your plugin during
+extractions - as it is unsure if the intended plugin is processing traces.
org.hansken.plugin-info.resource-max_cpu (in millicpu, optional)
+
org.hansken.plugin-info.resource-max_mem (in mbs, optional)
+
org.hansken.plugin-info.bulk-mode (optional, false by default)
+
org.hansken.plugin-info.transformers (the signatures of the transformer methods as JSON). The transformers field contains a JSON array and is structured as follows:
Welcome to the Hansken extraction plugin SDK documentation for plugin developers.
+This documentation describes the Hansken extraction plugin Software Development Kit (SDK).
+If you are new here, you can start by reading the introduction.
+
+
Attention
+
Hansken extraction plugins is a technology preview.
+Please don’t consider the SDK and integration in Hansken to be fully stable.
addTracelet(type, callback)