<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>CookBook of dyer</title>
<meta name="robots" content="noindex" />
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="intro.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="dyer-cli/index.html"><strong aria-hidden="true">2.</strong> Dyer-cli</a></li><li class="chapter-item expanded "><a href="actor/index.html"><strong aria-hidden="true">3.</strong> Actor</a></li><li class="chapter-item expanded "><a href="middleware/index.html"><strong aria-hidden="true">4.</strong> Middleware</a></li><li class="chapter-item expanded "><a href="database/index.html"><strong aria-hidden="true">5.</strong> Pipeline & Database Intergration</a></li><li class="chapter-item expanded "><a href="affix/index.html"><strong aria-hidden="true">6.</strong> Affix</a></li><li class="chapter-item expanded "><a href="attr/index.html"><strong aria-hidden="true">7.</strong> Attribute</a></li><li class="chapter-item expanded "><a href="feedback/index.html"><strong aria-hidden="true">8.</strong> Problem & Feedback</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">CookBook of dyer</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
<p><a href="https://github.com/homelyguy/dyer">dyer</a> is designed for reliable, flexible and fast Request-Response based service, including data processing, web-crawling and so on, providing some friendly, flexible, comprehensive features without compromising speed.</p>
<p><code>dyer</code> provides some high-level features:</p>
<ul>
<li>asynchronous, lock-free, concurrent streaming and I/O, make the best of thread pool, network, and system resource.</li>
<li>Event-driven, once you set the initials and recursive conditions, <code>dyer</code> will handle
the rest of it.</li>
<li>User-friendly and flexible, <code>dyer</code> offers high-level, easy to use wrappers and APIs what does a lot for you.</li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="dyer-cli"><a class="header" href="#dyer-cli">Dyer-cli</a></h1>
<p>Before stepping into the topic, <a href="https://crates.io/crates/dyer-cli">dyer-cli</a> is highly recommanded to be installed.
Dyer-cli is a handy tool for your easy and fast use of dyer, </p>
<h3 id="installation"><a class="header" href="#installation">Installation</a></h3>
<p><code>dyer-cli</code> is public crate, just run the following in the terminal,</p>
<pre><code class="language-bash">cargo install dyer-cli
</code></pre>
<p>once installed, type <code>dyer</code> in the terminal to check, if something like following it is successfully installed.</p>
<pre><code>Handy tool for dyer
USAGE:
dyer [subcommand] [options]
eg. dyer new myproject --debug
...
</code></pre>
<h3 id="create-project"><a class="header" href="#create-project">Create Project</a></h3>
<p>Dyer-cli generates a template that contains many useful instances and instructions
when using dyer with
following code:</p>
<pre><code class="language-bash">dyer new myproject
</code></pre>
<p>It will create a project called <code>myproject</code> and the files layout displays:</p>
<pre><code class="language-bash">|___Cargo.toml
|___Readme.md
|___data/
|___data/tasks/
|___src/
|___src/affix.rs
|___src/entity.rs
|___src/parser.rs
|___src/actor.rs
|___src/middleware.rs
|___src/pipeline.rs
</code></pre>
<h3 id="project-layout-and-its-role"><a class="header" href="#project-layout-and-its-role">Project layout and its role</a></h3>
<p>Main functionality of each file:</p>
<ul>
<li>the <code>affix.rs</code> serves as an actor to adjust and satisfy additional requirement</li>
<li>the <code>entity.rs</code> contains entities/data structure to be used/collected</li>
<li>the <code>parser.rs</code> contains functions that extract entities from response</li>
<li>the <code>actor.rs</code> contains initial when opening and final things to do when closing</li>
<li>the <code>middleware.rs</code> contains Some middlewares that process data at runtime</li>
<li>the <code>pipeline.rs</code> contains entities manipulation including data-storage, displsying and so on</li>
<li>the <code>lib.rs</code> exports all modules inside the directory, just do nothing here normally</li>
<li><code>Cargo.toml</code> is the basic configuration of the project</li>
<li><code>README.md</code> contains some instructions of the project</li>
<li><code>data/</code> place to store/load files of <code>App</code> when load-balancing and backup</li>
</ul>
<h3 id="basic-procedures"><a class="header" href="#basic-procedures">Basic Procedures</a></h3>
<p>Then it is your show time, basically there are simple example items(<code>function</code>, <code>enum</code>, <code>struct</code>)
in each file you can follow. After that check your code</p>
<pre><code class="language-bash">dyer check
</code></pre>
<p>if you run it the first time, dyer-cli will download the crates and then check the code.
if some warning happens such as <code>unused import</code> or <code>dead code</code> the command does a lot for you:</p>
<pre><code class="language-bash">dyer fix
</code></pre>
<p>A wraper of <code>cargo fix</code>, if some warning happens such as <code>unused import</code> or <code>dead code</code> the command does a lot for you. However it won't help if some errors occur, if so, you have to debug the code manually.</p>
<p>Edit <code>dyer.cfg</code> file in the root directory</p>
<p>the file contains some configurations of <code>ArgApp</code> that will update periodically, for more details see
[dyer.cfg Configuration]</p>
<p>When the program compiles, haha run it:</p>
<pre><code class="language-bash">dyer run
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="actor"><a class="header" href="#actor">Actor</a></h1>
<p>Actor is trait that processing some methods to set up necessary conditions before the whole programs starts/ends. it basically categorizes:</p>
<ul>
<li><strong>Preparations Beforehand</strong> method <code>new</code>, <code>open_actor</code> and <code>close_actor</code> are provided to serve that purpose. First of all, you can define a struct according to your requirement, eg:</li>
</ul>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[dyer::actor]
struct MyActor {
start_uris: Vec<String>
...
}
<span class="boring">}
</span></code></pre></pre>
<p>the struct <code>MyActor</code> should contain appropirate fields which initialized by the method <code>new</code>. </p>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[dyer::async_trait]
impl Actor<_, _> for MyActor {
async fn new() -> Self {
Self {
start_uris: vec!["https://example.domain/path/to/site1".to_string(),
"https://example.domain/path/to/site2".to_string() ]
//other fields
...
}
}
// other method of Actor
...
}
<span class="boring">}
</span></code></pre></pre>
<p>before the whole program starts, the method <code>open_actor</code> gets called. preparation should be done here! but wait, what should we do here? let's extend the example above a little bit.</p>
<blockquote>
<p>all start uris are stored by lines in a file <code>uris.txt</code></p>
</blockquote>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[dyer::actor]
pub struct MyActor {
start_uris: Vec<String>
}
#[dyer::async_trait]
impl Actor<_, _> for MyActor {
async fn new() -> Self {
use std::io::Read;
let mut file = std::fs::File::open("path/to/uris.txt").unwrap();
let buf = std::io::BufReader::new(file);
let uris = buf.lines().map(|line| {
line.unwrap()
}).collect::<Vec<String>>();
Self {
start_uris: uris
}
}
async fn open_actor(&mut self, _app: &mut App<_>) {
self.start_uris.for_each(|uri| {
Task::get(uri)
.parser(..)
.body(Body::empty(), "myactor_identifier".into())
.unwrap()
});
}
// other method of Actor
...
}
<span class="boring">}
</span></code></pre></pre>
<p>Analogously you can do some staff with <code>close_actor</code> when program ends.</p>
<ul>
<li><strong>Assignments Entry</strong> The program cannot starts without <code>Task</code>, <code>entry_task</code> serve as a way to add tasks to the lists. It expects a vector of <code>Task</code> when the function ends,</li>
</ul>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[dyer::async_trait]
impl Actor<_, _> for MyActor {
async fn entry_task(&mut self) -> Result<Vec<Task>, Box<dyn Error>> {
self.start_uris.map(|uri| {
Task::get(uri)
.parser(..)
.body(Body::empty(), "myactor_identifier".into())
.unwrap()
}).collect::<_>()
}
// other method of Actor
...
}
<span class="boring">}
</span></code></pre></pre>
<p>As for <code>entry_affix</code>, it is commonly not necessary unless modification is required for that <code>Task</code>,
But what is that? before we answer that let's take a look at the structure of <a href="https://docs.rs/dyer/latest/dyer/struct.Task.html">Task</a>, </p>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Task {
/// main infomation that represents a `Task`
pub(crate) inner: InnerTask,
/// Formdata, files or other request parameters stored here
pub(crate) body: Body,
...
}
pub struct InnerTask {
pub uri: Uri,
/// request's vesoin
pub method: Method,
/// additional headers if necessary
pub headers: HeaderMap<HeaderValue>,
...
}
<span class="boring">}
</span></code></pre></pre>
<p>it is obvious to see that a <code>Task</code> almost contains infomation to make a request.</p>
<p>But when does <code>entry_affix</code> play its role? Here are some scenarios that you may use it.</p>
<ol>
<li>Headers Modification (eg. Cookies, User-Agents, Tokens, and etc.)</li>
<li>javascript simulation</li>
<li>FFI and others</li>
</ol>
<p>Here we focus on the first one(most used) and an example is given at section <a href="actor/../actor/readme.html">Actor</a>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="middleware"><a class="header" href="#middleware">Middleware</a></h1>
<p>Middleware hooks all requests/responses and their derivatives of dyer, including <code>Task</code>, <code>Affix</code>, <code>Request</code>, <code>Response</code>, <code>error</code> and <code>entiry</code>. it's flexible, low-level, scale to modify the data flow of dyer.</p>
<h3 id="inspection-of-middleware"><a class="header" href="#inspection-of-middleware">Inspection of Middleware</a></h3>
<p>before we dive deeper into what middleware is, let take a look at some simplified code of <code>Middleware</code></p>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct MiddleWare<'md, E> {
handle_affix:
Option<&'md dyn for<'a> Fn(&'a mut Vec<Affix>, &'a mut App<E>)>,
handle_task:
Option<&'md dyn for<'a> Fn(&'a mut Vec<Task>, &'a mut App<E>)>,
handle_req:
Option<&'md dyn for<'a> Fn(&'a mut Vec<Request>, &'a mut App<E>)>,
handle_res:
Option<&'md dyn for<'a> Fn(&'a mut Vec<Response>, &'a mut App<E>)>,
handle_entity:
Option<&'md dyn for<'a> Fn(&'a mut Vec<E>, &'a mut App<E>)>,
handle_yerr: Option<
&'md dyn for<'a> Fn(
&'a mut Vec<Result<Response, MetaResponse>>,
&'a mut App<E>,
)>,
handle_err: Option<
&'md dyn for<'a> Fn(
&'a mut Vec<Result<Response, MetaResponse>>,
&'a mut App<E>,
)>,
// some other fields
...
}
<span class="boring">}
</span></code></pre></pre>
<p>As shown above, it accepts some nullable async function as handlers for requests, response and its derivatives.
let's log out errors:</p>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub async fn log_err(errs: &mut Vec<Result<Response, MetaResponse>, _: &mut App<E>> {
for r in errs.iter() {
match r {
Ok(data) => {
println!("failed request to {}", data.metas.info.uri);
},
Err(e) => {
println!("failed request to {}", e.info.uri);
}
}
}
}
// set up `handle_err`
let middleware = MiddleWare::builder().err_mut(&log_err).build("marker".into());
<span class="boring">}
</span></code></pre></pre>
<p>that middleware will log out uri of failed response.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="pipeline--database-intergration"><a class="header" href="#pipeline--database-intergration">Pipeline & Database Intergration</a></h1>
<p>the end of data flow, it will be consumed.
When an entity has been collected, it eventually will be sent to pipelines.
Pipeline provides way to do:</p>
<ul>
<li>cleaning/validating collected entity </li>
<li>de-duplicates </li>
<li>database storing</li>
</ul>
<h3 id="inspection-of-pipeline"><a class="header" href="#inspection-of-pipeline">Inspection of Pipeline</a></h3>
<p>Let's take a look at the simplified code of <code>Pipeline</code> before diving deeper.</p>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct PipeLine<'pl, E, C> {
initializer: Option<&'pl dyn for<'a> Fn(&'a mut App<E>) -> Option<C>>,
disposer: Option<&'pl dyn for<'a> Fn(&'a mut App<E>)>,
process_entity:
Option<&'pl dyn for<'a> Fn(Vec<E>, &'a mut App<E>)>,
process_yerr: Option<
&'pl dyn for<'a> Fn(
Vec<Result<Response, MetaResponse>>,
&'a mut App<E>,
)>,
// other fields omitted
...
}
<span class="boring">}
</span></code></pre></pre>
<ul>
<li>the method <code>initializer</code> get called only once over the runtime, it returns a generic type <code>C</code> which defined by user, the generic type is usually a connection cursor to storage destination. </li>
<li>the method <code>disposer</code> get called once when the pipeline ends. </li>
<li>the method <code>process_entity</code> processes a vector of entity then consume them.</li>
<li>the method <code>process_yerr</code> processes a vector of failed response then consume them.</li>
</ul>
<h3 id="diesel-sql"><a class="header" href="#diesel-sql">Diesel Sql</a></h3>
<p><a href="https://diesel.rs">Diesel</a> is the most productive way to interact with SQL databases. It is recommanded to get around the basics of diesel <a href="https://diesel.rs/guides/getting-started"> here </a>.
A detailed example is given at <a href="https://github.com/HomelyGuy/dyer/tree/master/examples/dyer-diesel">examples</a>.</p>
<h3 id="other-database"><a class="header" href="#other-database">Other Database</a></h3>
<p>Almost other databases are equipmented with rust-based driver, it is just as simple as following the documentation, implementing the necessary methods.</p>
<p>Here is an simple example for MongoDB Intergration with driver <a href="https://crates.io/crates/mongodb">mongodb</a>.</p>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub async fn establish_connection(_app: &mut App<_>) -> Option<&'static mongodb::Client> {
static INIT: Once = Once::new();
static mut VAL: Option<mongodb::Client> = None;
unsafe {
let uri = "mongodb://127.0.0.1:27017";
INIT.call_once(|| {
VAL = Some(mongodb::Client::with_uri_str(uri).await.unwrap());
});
VAL.as_ref()
}
}
pub async fn store_item(ens: Vec<_>, _app: &mut App<_>) {
// do stuff here like validating and dropping
...
let client = establish_connection(_app).await;
client.database("database_name_here")
.collection("collection_name_here")
.insert_one(...)
.await
.unwrap();
}
// set up pipiline
let pipeline = Pipeline::builder()
.initializer(establish_connection)
.entity_mut(store_item)
.build("marker".into());
<span class="boring">}
</span></code></pre></pre>
<p>This pipeline will insert collected entity into MongoDB.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="affix"><a class="header" href="#affix">Affix</a></h1>
<p>Affix is the fringerprint when making a request. In general, affix is not necessay unless the target site requires visitor meet some criteria. Affix, by far, mainly focus on modification of Headers.</p>
<blockquote>
<p>assign a user-agent for each Task with file <code>user-agents.txt</code> containing user-agents by lines</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust no_run">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// src/affix.rs
pub struct Aff {
uas: Vec<String>
iter: std::iter::Cycle<String>,
}
#[dyer::async_trait]
impl Affixor for Aff {
// this function only runs once
async fn init(&mut self) {
use std::io::Read;
let mut file = std::fs::File::open("path/to/user-agents.txt").unwrap();
let buf = std::io::BufReader::new(file);
let uas = buf.lines().map(|line| {
line.unwrap()
}).collect::<Vec<String>>();
self.uas = uas;
self.iter = self.uas.iter().cycle();
}
// if the affix isn't obtained via network(request-response), just return `None`
async fn invoke(&mut self) -> Option<dyer::Request> {
None
}
// dyer combine the `Affix` returned by this function to each `Task` before make an request
async fn parse(&mut self, _: Option<Result<Response, MetaResponse>>) -> Option<dyer::Affix> {
// return the user-agent in order
self.iter.next().to_owned()
}
// other method of Affixor
...
}
// src/actor.rs
#[dyer::async_trait]
impl Actor<_, _> for MyActor {
async fn entry_affix(&mut self) -> Option<Aff> {
Some(Aff)
}
// other method of Actor
...
}
<span class="boring">}
</span></code></pre></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="attribute"><a class="header" href="#attribute">Attribute</a></h1>
<p>You may notice that some components are annotated with something like <code>#[dyer::entity]</code>, <code>#[dyer::actor]</code> or others, they are attribute Macros what transforms a block of code into another code block.
All of availiable attributes are following.</p>
<ul>
<li><code>#[dyer::affix]</code> mark the type annotated for <code>Affix</code> </li>
<li><code>#[dyer::actor]</code> mark the type annotated for <code>Actor</code> </li>
<li><code>#[dyer::middleware]</code> mark the type annotated for <code>Middleware</code> </li>
<li><code>#[dyer::pipeline]</code> mark the type annotated for <code>Pipeline</code> </li>
<li><code>#[dyer::parser]</code> mark the type annotated for <code>parser</code>, any function with this attribute can parse response.</li>
<li><code>#[dyer::entity]</code> mark the type annotated for <code>entity</code>, any type with this attribute can contain data to be collected. </li>
<li><code>#[dyer::async_trait]</code> mark the type annotated for <code>async_trait</code>, note that it is a wrapper of crate <a href="https://crates.io/crates/async-trait">async_trait</a> </li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="problem--feedback"><a class="header" href="#problem--feedback">Problem & Feedback</a></h1>
<p>It is, of course, probable that bugs and errors lie in somewhere, and defects may
appear in an
unexpected way, if you got any one, comments and suggestions are welcome, please new a issue in
<a href="https://github.com/HomelyGuy">my github</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</nav>
</div>
<!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript">
var socket = new WebSocket("ws://localhost:3000/__livereload");
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
<script type="text/javascript">
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</body>
</html>