Files
docs/hexa/wiki/06_best_practices/index.html

1072 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="prev" href="../05_philosophy/">
<link rel="next" href="../07_error_handling/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
<title>Best Practices - Hexa Documentation</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://unpkg.com/dracula-prism/dist/css/dracula-prism.css">
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="blue-grey" data-md-color-accent="teal">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#best-practices" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Hexa Documentation" class="md-header__button md-logo" aria-label="Hexa Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.25 2c.19 0 .36.11.44.26l2.22 3.96.09.28-.09.28-2.22 3.96c-.08.15-.25.26-.44.26h-4.5c-.19 0-.36-.11-.44-.26L3.09 6.78 3 6.5l.09-.28 2.22-3.96c.08-.15.25-.26.44-.26zm0 11c.19 0 .36.11.44.26l2.22 3.96.09.28-.09.28-2.22 3.96c-.08.15-.25.26-.44.26h-4.5c-.19 0-.36-.11-.44-.26l-2.22-3.96L3 17.5l.09-.28 2.22-3.96c.08-.15.25-.26.44-.26zm9.25-5.5c.19 0 .36.11.44.26l2.22 3.96.09.28-.09.28-2.22 3.96c-.08.15-.25.26-.44.26H15c-.19 0-.36-.11-.44-.26l-2.22-3.96-.09-.28.09-.28 2.22-3.96c.08-.15.25-.26.44-.26z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Hexa Documentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Best Practices
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href=".." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../01_overview/" class="md-tabs__link">
Overview
</a>
</li>
<li class="md-tabs__item">
<a href="../02_components/" class="md-tabs__link">
Core Components
</a>
</li>
<li class="md-tabs__item">
<a href="../03_use_cases/01_yaml_first/" class="md-tabs__link">
Use Cases
</a>
</li>
<li class="md-tabs__item">
<a href="../04_design/" class="md-tabs__link">
Design
</a>
</li>
<li class="md-tabs__item">
<a href="../05_philosophy/" class="md-tabs__link">
Philosophy
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="./" class="md-tabs__link">
Best Practices
</a>
</li>
<li class="md-tabs__item">
<a href="../07_error_handling/" class="md-tabs__link">
Error Handling
</a>
</li>
<li class="md-tabs__item">
<a href="../08_testing_example/" class="md-tabs__link">
Testing
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted md-nav--integrated" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Hexa Documentation" class="md-nav__button md-logo" aria-label="Hexa Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.25 2c.19 0 .36.11.44.26l2.22 3.96.09.28-.09.28-2.22 3.96c-.08.15-.25.26-.44.26h-4.5c-.19 0-.36-.11-.44-.26L3.09 6.78 3 6.5l.09-.28 2.22-3.96c.08-.15.25-.26.44-.26zm0 11c.19 0 .36.11.44.26l2.22 3.96.09.28-.09.28-2.22 3.96c-.08.15-.25.26-.44.26h-4.5c-.19 0-.36-.11-.44-.26l-2.22-3.96L3 17.5l.09-.28 2.22-3.96c.08-.15.25-.26.44-.26zm9.25-5.5c.19 0 .36.11.44.26l2.22 3.96.09.28-.09.28-2.22 3.96c-.08.15-.25.26-.44.26H15c-.19 0-.36-.11-.44-.26l-2.22-3.96-.09-.28.09-.28 2.22-3.96c.08-.15.25-.26.44-.26z"/></svg>
</a>
Hexa Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../01_overview/" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../02_components/" class="md-nav__link">
<span class="md-ellipsis">
Core Components
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_4" >
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="0">
<span class="md-ellipsis">
Use Cases
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_4">
<span class="md-nav__icon md-icon"></span>
Use Cases
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_4_1" >
<label class="md-nav__link" for="__nav_4_1" id="__nav_4_1_label" tabindex="0">
<span class="md-ellipsis">
Contract Authoring
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_4_1_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_4_1">
<span class="md-nav__icon md-icon"></span>
Contract Authoring
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../03_use_cases/01_yaml_first/" class="md-nav__link">
<span class="md-ellipsis">
YAML First
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_use_cases/02_abc_first/" class="md-nav__link">
<span class="md-ellipsis">
ABC First
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_4_2" >
<label class="md-nav__link" for="__nav_4_2" id="__nav_4_2_label" tabindex="0">
<span class="md-ellipsis">
Wiring
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_4_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_4_2">
<span class="md-nav__icon md-icon"></span>
Wiring
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../03_use_cases/03_wiring_runtime/" class="md-nav__link">
<span class="md-ellipsis">
Wiring & Runtime
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../04_design/" class="md-nav__link">
<span class="md-ellipsis">
Design
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../05_philosophy/" class="md-nav__link">
<span class="md-ellipsis">
Philosophy
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Best Practices
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Best Practices
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#layer-discipline" class="md-nav__link">
<span class="md-ellipsis">
🌳 Layer Discipline
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#only-redeclare-what-you-change" class="md-nav__link">
<span class="md-ellipsis">
👣 Only Redeclare What You Change
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#representational-fidelity" class="md-nav__link">
<span class="md-ellipsis">
📄 Representational Fidelity
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wiring-discipline" class="md-nav__link">
<span class="md-ellipsis">
🧩 Wiring Discipline
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#test-layout" class="md-nav__link">
<span class="md-ellipsis">
🧪 Test Layout
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../07_error_handling/" class="md-nav__link">
<span class="md-ellipsis">
Error Handling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../08_testing_example/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="best-practices">Best Practices<a class="headerlink" href="#best-practices" title="Permanent link">&para;</a></h1>
<p>Rules of thumb for structuring hexa pipelines that stay easy to extend and verify. Grounded in the extraction pipeline sample and the test suite.</p>
<hr />
<h2 id="layer-discipline">🌳 Layer Discipline<a class="headerlink" href="#layer-discipline" title="Permanent link">&para;</a></h2>
<ul>
<li><strong>Never subclass an ABC for a bank.</strong> Bank classes always subclass the concrete <code>Impl*</code> classes. The ABC defines the contract once; <code>Impl*</code> carries behavior; banks only vary.</li>
<li><strong>Pin concrete types at <code>Impl*</code></strong>, not at the ABC. The ABC declares <em>shape</em> (<code>number: NumberPort</code>); <code>Impl*</code> decides <em>implementation</em> (<code>number: ImplNumberPort</code>).</li>
<li><strong>Keep the root thin.</strong> A bank root is one annotated subclass:
<div class="language-python highlight"><span class="filename">Python</span><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="k">class</span><span class="w"> </span><span class="nc">AxisExtractionPipeline</span><span class="p">(</span><span class="n">ImplExtractionPipeline</span><span class="p">):</span>
<a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a> <span class="n">txn_dicts</span><span class="p">:</span> <span class="n">AxisTxnDictsPort</span>
</code></pre></div></li>
</ul>
<hr />
<h2 id="only-redeclare-what-you-change">👣 Only Redeclare What You Change<a class="headerlink" href="#only-redeclare-what-you-change" title="Permanent link">&para;</a></h2>
<p>The redundancy rule: if a slot's behavior is already correct from <code>Impl*</code>, <strong>leave it out</strong>.</p>
<ul>
<li><code>date</code> is not redeclared on <code>AxisTransactionParserPort</code> — it inherits <code>ImplDatePort</code>.</li>
<li><code>ambiguity</code> is not redeclared on <code>AxisAmountBalancePort</code> — it inherits <code>ImplAmbiguityPort</code>.</li>
<li>Re-declaring slots you don't change adds mental load on the extender with zero behavior change — and keeps the diff between a bank and <code>Impl*</code> minimal.</li>
</ul>
<p>If you do redeclare a slot, <strong>re-pin it back to your own bank's port</strong> (e.g. <code>amount_balance.number: AxisNumberPort</code>) so a single bank's number formatting is consistent down the subtree.</p>
<hr />
<h2 id="representational-fidelity">📄 Representational Fidelity<a class="headerlink" href="#representational-fidelity" title="Permanent link">&para;</a></h2>
<p>Keep YAML and ABC in agreement:</p>
<ul>
<li>Every converter (<code>parse_yaml</code>, <code>generate_abc</code>, <code>parse_abc</code>, <code>generate_yaml</code>) serializes through the same <code>PortNode</code> tree — any tree fits, nothing is tied to a particular sample.</li>
<li>Use <code>check_matches(spec.yaml, abc.py)</code> (or <code>hexa check ...</code>) as a CI guard whenever both representations are maintained.</li>
<li>Class-level attributes on ABCs are used as <strong>documentation of the config surface</strong>; pin defaults at the <code>Impl*</code> level so the ABC stays a pure contract.</li>
<li>Nested grouped values are expressed once per representation: a <code>@dataclass</code> annotation (ABC) ⇄ a nested struct mapping (YAML).</li>
</ul>
<hr />
<h2 id="wiring-discipline">🧩 Wiring Discipline<a class="headerlink" href="#wiring-discipline" title="Permanent link">&para;</a></h2>
<ul>
<li>Put plain runtime objects that must not be re-created (repos, handlers, clients) in <code>instances=</code> — wired verbatim by name, never reconstructed.</li>
<li>Put plain config values in <code>config=</code> — propagated <strong>by name</strong> across the whole tree, including nested ports.</li>
<li>Name a slot whatever the annotated field is called; <code>build</code> matches <code>instances</code>/<code>config</code> by that annotated name, so names must agree across the tree.</li>
<li>Keep port slot annotations to concrete <code>Impl*</code>/bank classes (or ABCs when the leaf is a config-only option). Builtin/<code>typing</code>/union-annotated fields are treated as config, not ports — so don't annotate a port with <code># type: str</code>.</li>
</ul>
<hr />
<h2 id="test-layout">🧪 Test Layout<a class="headerlink" href="#test-layout" title="Permanent link">&para;</a></h2>
<p>Mirror <code>tests/</code>:</p>
<ul>
<li>One test module per component (<code>test_parse_yaml.py</code>, <code>test_parse_abc.py</code>, <code>test_generate_abc.py</code>, ...).</li>
<li>A dedicated <code>test_container.py</code> for <code>build</code> behavior (injection semantics, config propagation, defaults).</li>
<li>Use <code>samples/minimal</code> as the smallest contrived base fixture: one root, one nested port, a few config vars, a single abstract method. It bikes the whole YAML ⇄ ABC ⇄ runtime loop in a few lines.</li>
<li>Test the round-trip property directly — parsed models from both representations must be equal (<code>node1.diff(node3) == []</code>) and regeneration must be idempotent.</li>
</ul>
<hr />
<h2 id="related">Related<a class="headerlink" href="#related" title="Permanent link">&para;</a></h2>
<ul>
<li><a href="../03_use_cases/03_wiring_runtime/">03 Wiring &amp; runtime</a> · <a href="../07_error_handling/">07 Error Handling</a> · <a href="../08_testing_example/">08 Testing</a></li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../05_philosophy/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Philosophy">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
Philosophy
</div>
</div>
</a>
<a href="../07_error_handling/" class="md-footer__link md-footer__link--next" aria-label="Next: Error Handling">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Error Handling
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.expand", "navigation.sections", "navigation.footer", "navigation.top", "navigation.instant", "toc.integrate", "content.code.annotate", "content.code.copy", "content.code.select", "content.code.line_numbers", "content.tabs.link", "content.tooltips", "content.action.edit", "search.suggest", "search.highlight", "search.share", "header.autohide", "announce.dismiss", "footer.social"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>